今天又遇到一个恶搞问题,内存对齐 为什么会出现内存对齐? 效率问题,对于结构体,访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。 为什么要考虑内存对齐? 跨平台,跨语言,跨编译器的时候因为内存对齐的原因可能造成设置的数据和获取的数据不同。 举几个简单的例子,假如您在32位操作系统下将上面的结构体直接以取地址的方式把该结构体写入到一个文件中,再在64位操作系统下读取,直接将内存付给这个结构体,就会出现问题。 再比如我使用上面的结构体编译一个C++程序,直接将该结构体对应的内存发送给一个Java程序,Java程序在读取这段数据的时候就麻烦了。 所以为了避免内存对齐,我建议自己定义数据,显式的将结构体中的数据按照既定的规则写到数据流中。虽然这样可以避免内存对齐,但是大家也应该了解一下内存对齐。 内存对齐和编译器有关。 编译器有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编译命令#pragma VS2008的对齐系数默认为8。 举例说明: struct { char a; short b; int c; }; int main() { Test test.a = 0x12; test.b = 0x1234; test.c = 0x12345678; int size = sizeof(Test); Test *point = &test; return 0; } 运行之后得到: size为8 内存排列方式为: ![]() 因为我的电脑的CPU是intel的,所以在内存中小端存储,也就是你看到的c是按照byte倒叙存储的(CPU大小端问题将会在以后的章节中详细说明) 但是在中间有一byte的空白内存,这就是编译器内存对齐造成的。 而如果数据结构是这样的(a和c的顺序变了): struct { char a; int c; short b; }; 运行之后的结果是: size的值为12 内存的排列方式为: ![]()
内存对齐的规则: VS2008默认#pragma pack(n)中的n=8 struct { char a; n=8 char=1 , 对齐起始地址为1的倍数,所以a的起始内存地址为0,内存区间[0,0] short b; n=8 short=2, 对齐起始地址为2的倍数,所以b的起始内存地址为2,内存区间[2,3] int c; n=8 int=4, 对齐起始地址为4的倍数,所以b的起始内存地址为4,内存区间[4,7] }; #pragma struct { char a; n=1 char=1 ,对齐起始地址为1的倍数,所以a的起始内存地址为0,内存区间[0,0] short b; n=1 short=2,对齐起始地址为1的倍数,所以b的起始内存地址为1,内存区间[1,2] int c; n=1 int=4, 对齐起始地址为1的倍数,所以b的起始内存地址为3,内存区间[3,6] }; 还存在一种情况: VS2008默认#pragma pack(n)中的n=8 struct { double e; char f; }; int size =sizeof(Test); size的值为16 (注:补齐,保证最终大小是8的倍数) #pragma struct { double e; char f; }; int size =sizeof(Test); size的值为12 (注:补齐,保证最终大小是4的倍数) 上面两种情况的内存排列是一致的,但是大小是不一样的。 据说GCC和VS2008是不一样的,没有测试 下一次有时间试一下。 对于内存对齐的问题,我的建议就是显式组织数据,尤其对于网络数据传输和读写文件,不要直接取地址。 |
|