Nội dung bài viết
Đăng ký học lập trình C++
Tại STDIO bạn được dạy nền tảng lập trình tốt nhất.
Đăng ký học
Nguyễn Nghĩa C++ có cung cấp cho chúng ta một số kiểu dữ liệu nguyên thủy được xây dựng sẵn (built-in) như char, int, float, double, long... C++ cũng cho phép chúng ta tạo ra những kiểu dữ liệu mới như struct, class. Việc cấp phát và tổ chức bộ nhớ đối với dữ liệu kiểu nguyên thủy là khá đơn giản, còn đối với struct, class thì cấp phát, tổ chức bộ nhớ như thế nào. Trong bài viết này tôi sẽ phân tích, làm rõ vấn đề này một cách cụ thể.

Giới thiệu

C++ có cung cấp cho chúng ta một số kiểu dữ liệu nguyên thủy được xây dựng sẵn (built-in) như char, int, float, double, long... C++ cũng cho phép chúng ta tạo ra những kiểu dữ liệu mới do người dùng định nghĩa như struct, class. Việc cấp phát và tổ chức bộ nhớ đối với dữ liệu kiểu nguyên thủy là khá đơn giản, còn đối với struct, class thì cấp phát, tổ chức bộ nhớ như thế nào. Trong bài viết này tôi sẽ phân tích, làm rõ vấn đề này một cách cụ thể.

Tiền đề bài viết

Bài viết mong muốn chia sẽ kiến thức hữu ích cho các độc giả.
Những ví dụ trong bài viết tôi sử dụng Visual Studio 2013 trên hệ điều hành Windows 8.1 64bit.

Đối tượng hướng đến

Những lập trình viên có kiến thức vững chắc về C++ đặc biệt là việc cấp phát và tổ chức bộ nhớ. Các bạn có thể xem qua bài viết struct Và union :: http://www.stdio.vn/articles/read/166/struct-va-union để nắm lại kiến thức về struct trước khi bắt đầu vào bài viết.

Các kiểu dữ liệu nguyên thuỷ trong C++

C++ có cung cấp cho chúng ta các kiểu dữ liệu được xây dựng sẵn. Thông tin chi tiết về các kiểu dữ liệu này được thể hiện ở bảng dưới:

KIỂU DỮ LIỆU KÍCH THƯỚC MIỀN GIÁ TRỊ
char 1byte -127 to 127 or 0 to 255
unsigned char 1byte 0 to 255
signed char 1byte -127 to 127
int 4bytes   -2147483648 to 2147483647
unsigned int 4bytes  0 to 4294967295
signed int 4bytes -2147483648 to 2147483647
short int 2bytes -32768 to 32767
unsigned short int   2bytes 0 to 65,535
signed short int   2bytes -32768 to 32767
long int  4bytes -2,147,483,647 to 2,147,483,647
signed long int 4bytes -2,147,483,647 to 2,147,483,647
unsigned long int 4bytes 0 to 4,294,967,295
float       4bytes +/- 3.4e +/- 38 (~7 digits)
double    8bytes +/- 1.7e +/- 308 (~15 digits)
long double 8bytes +/- 1.7e +/- 308 (~15 digits)

Trước khi đi vào chủ đề của bài viết tôi muốn các bạn hiểu rõ những biến này được tổ chức như thế nào trên bộ nhớ. Giả sử tôi có những câu lệnh dưới đây:

int id = 13520546;        //4bytes
double salary = 500.000;  //8bytes

Và tổ chức bộ nhớ của chúng trong chương trình.

ss_1

Vùng nhớ của id và salary có thể là không liên tục và nằm rời rạc trên bộ nhớ.

Kết quả khi chạy chương trình dưới đây và in ra địa chỉ của hai biến id và salary:

int id = 13520546;
double salary = 500.000;
printf("id address: %d\nsalary address: %d\n", &id, &salary);

ss_2

Tổ chức bộ nhớ trong struct, class

Tôi tạo một struct Point thể hiện tọa độ của điểm trong không gian hai chiều:

struct Point
{
	int x;
	int y;
};

Tôi lấy kích thước của struct bằng cách sử dụng toán tử sizeof được cung cấp trong C++, và tôi thấy được kích thước của nó là 8bytes đúng bằng các kích thước các trường dữ liệu thành viên công lại. Và dễ hình dùng hơn struct Point này được tổ chức trên bộ nhớ như sau:

ss_3

Tiếp thep tôi tạo tiếp một struct Student thể hiện thông tin của một sinh viên gồm id, age, gpa là điểm trung bình học kì:

struct Student
{
	char id;
	int age;
	double gpa;
};

Tôi tiếp tục lấy sizeof của struct Student:

int size = sizeof(Student);

Các bạn nghĩ rằng biến size sẽ có giá trị là 13bytes bằng kích thước của id (1bytes) cộng với age (4bytes) và gpa (8bytes). Nhưng thực tế sau khi debug tôi thấy rằng biến size có giá trị là 16bytes chứ không phải là 13bytes. Vậy struct được cấp phát và tổ chức như thế nào ở trên bộ nhớ?.

Đầu tiên trình biên dịch sẽ lấy kích thước của trường dữ liệu thành viên có kích thước lớn nhất (theo đơn vị byte) mà kích thước đó là lũy thừa của 2.

Trong trường hợp này là 8bytes theo kích thước của gpa. Sau đó cấp phát 1 block gồm đúng bằng kích thước này là 8bytes, và "đẩy" id có kích thức 1 bytes vào:

ss_4

Ta vẫn còn 7 chổ trống, ở đây tùy theo trình biên dịch mà nó sẽ padding bao nhiêu bytes rồi "đẩy" age kiểu int có kích thước 4bytes vào. Ở đây tôi sử dụng trình biên dịch của Visual Stdio 2013 nên sẽ padding 3bytes sau đó "đẩy" age vào:

ss_5

Đã hết chổ trống, tiếp tục cấp thêm block 8 byte nữa và "đẩy" gpa kiểu double có kích thước 8byte vào:

ss_6

Vậy kích thước của struct này là 16bytes.

Vùng nhớ cấp phát cho struct luôn luôn là liên tục trong bộ nhớ như ở trên ta thấy là địa chỉ của id, age, gpa lần lượt là 0x04, 0x08, 0xC.

Tôi khảo sát thêm một struct nữa có tên là Demo

struct Demo
{
	int a, b;
    double c, d;
};

Kích thước struct này là: 24bytes;

Tôi sắp xếp lại các trường dữ liệu như sau:

struct Demo
{
    int a;
    double c, d;
    int b;
};

Kiểm tra thấy kích thức của struct này là 32bytes

Như vậy struct có cùng các trường dữ liệu như nhau, nhưng nếu thay đổi vị trí của chúng trong struct thì có thể thay đổi kích thước của struct đó. Dựa vào cách sắp xếp bộ nhớ trong struct mà tôi đã trình bày ở trên giúp các bạn có thể tránh được việc mất mát vùng nhớ.

Lưu ý: Cách tổ chức bộ nhớ trong class cũng tượng tự như struct.

Struct, class không có dữ liệu thành viên

Chúng ta đã khảo sát struct có các trường dữ liệu, nếu struct đó không có dữ liệu thành viên thì sao, việc lưu trữ như thế nào?. Một ví dụ điển hình là class xử lý toán học Math trong C#. 

struct Person
{
    
};

Tôi có 1 struct Person không có thành viên nào cả. Và tôi lấy sizeof của nó:

int size = sizeof(Person);

Debug thấy size có giá trị 1bytes. Thì đối với những struct, hay class như thế này thì trình biên dịch sẽ cấp phát 1 bytes để có thể lưu trữ nó dưới bộ nhớ.

Tiếp theo, tôi tạo 1 biến có tên là person và lấy địa chỉ của nó trong bộ nhớ: 

Person person;
printf("Address of person %d\n", &person);

Kết quả in lên màn hình

ss_7

Địa chỉ của struct, class

Cũng giống như mảng, địa chỉ của mảng chính là địa chỉ của phần tử đầu tiên, thì đối với class, struct cũng vậy. Địa chỉ của nó chính là địa chỉ của thành viên đầu tiên trong struct, class đó.

struct Student
{
    char id;
    int age;
    double gpa;
};

int main()
{
	Student student;
	printf("Address of student: %d\n", &student);
	printf("Address of student.id: %d\n", &student.id);
	printf("Address of student.age: %d\n", &student.age);
	printf("Address of student.gpa: %d\n", &student.gpa);
    return 0;
}

Và kết quả cho thấy rằng địa chỉ của student cũng chính là địa chỉ của  id trong struct đó:

ss_8

 

THẢO LUẬN
ĐÓNG