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.
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:
1 2 |
int id = 13520546; //4bytes double salary = 500.000; //8bytes |
Và tổ chức bộ nhớ của chúng trong chương trình.
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:
1 2 3 |
int id = 13520546; double salary = 500.000; printf("id address: %d\nsalary address: %d\n", &id, &salary); |
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:
1 2 3 4 5 |
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:
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ì:
1 2 3 4 5 6 |
struct Student { char id; int age; double gpa; }; |
Tôi tiếp tục lấy sizeof của struct Student:
1 |
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:
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:
Đã 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:
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, gpalầ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
1 2 3 4 5 |
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:
1 2 3 4 5 6 |
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#.
1 2 3 4 |
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ó:
1 |
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ớ:
1 2 |
Person person; printf("Address of person %d\n", &person); |
Kết quả in lên màn hình
Đị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 đó.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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 đó: