本文共 3512 字,大约阅读时间需要 11 分钟。
更改C编译器的缺省字节对齐方式
在缺省情况下,C编译器为每一个变量或是数据单元按其自然对界条件分配空间。一般地,可以通过下面的方法来改变缺省的对界条件:
· 使用伪指令#pragma pack (n),C编译器将按照n个字节对齐。
· 使用伪指令#pragma pack (),取消自定义字节对齐方式。
另外,还有如下的一种方式:
· __attribute((aligned (n))),让所作用的结构成员对齐在n字节自然边界上。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐。
· __attribute__ ((packed)),取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。
【转载例文】
1. 概述
本文讨论了结构的自然边界对齐,在缺省情况下,c编译器为每一个变量或数据单元按其自然边界对齐条件分配空间。 但可以通过四种方法来更改C编译器的缺省字节对齐方式,即可以指定边界对齐。 在阅读完本文档后,将会更深入地了解一个结构的sizeof到底应当是多少。2. 自然边界对齐 在C语言中,结构是一种复合数据类型,其构成元素既可以是基本数据类型(如int、long、float等)的变量,也可以是一些复合数据类型(如数组、结构、联合等)的数据单元。 结构字节对齐有以下几个特点: 1. 对于结构体,编译器会自动进行成员变量的对齐,以提高运算效率。 缺省情况下,编译器为结构的每个成员按其自然边界对齐( natural alignment)条件分配空间。 自然边界对齐即为默认对齐方式。 2. 各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构的地址相同。 3. 结构整体的默认对齐方式就是它的所有成员使用的对齐参数中最大的一个。 4. 结构整体长度的计算必须取所用过的所有对齐参数的整数倍,不够补空字节; 也就是取所用过的所有对齐参数中最大的那个值的整数倍,因为对齐参数都是2的n次方; 这样在处理数组时可以保证每一项都边界对齐; 例如,下面的结构各成员空间分配情况: struct test { char x1; short x2; float x3; char x4; }; 结构的第一个成员x1,其偏移地址为0,占据了第1个字节。 第二个成员x2为short类型,其起始地址必须2字节边界对齐,因此,编译器在x2和x1之间填充了一个空字节。 结构的第三个成员x3和第四个成员x4恰好落在其自然边界对齐地址上,在它们前面不需要额外的填充字节。 在test结构中,成员x3要求4字节边界对齐,是该结构所有成员中要求的最大边界对齐单元,因而test结构的自然边界对齐条件为4字节,编译器在成员x4后面填充了3个空字节。整个结构所占据空间为12字节。3. 指定边界对齐 在缺省情况下,c编译器为每一个变量或数据单元按其自然边界对齐条件分配空间;但可以通过下面四种方法来更改C编译器的缺省字节对齐方式:方法1: 使用#pragma pack #pragma pack说明: 1. pack提供数据声明级别的控制,对定义不起作用; 2. 调用pack时不指定参数,将恢复C编译器的缺省字节对齐方式,即使用伪指令#pragma pack()将取消自定义字节对齐方式; 3. 一旦改变数据类型的alignment,直接效果就是占用memory的减少,但是performance可能会下降; #pragma pack语法详细说明: 1. show:可选参数;显示当前packing aligment的字节数,以warning message的形式被显示; 2. push:可选参数;将当前指定的packing alignment数值进行压栈操作,这里的栈是the internal compiler stack,同时设置当前的packing alignment为n;如果n没有指定,则将当前的packing alignment数值压栈; 3. pop:可选参数;从internal compiler stack中删除最顶端的record;如果没有指定n,则当前栈顶record即为新的packing alignment数值;如果指定了n,则n将成为新的packing aligment数值;如果指定了identifier,则internal compiler stack中的record都将被pop直到identifier被找到,然后pop出identitier,同时设置packing alignment数值为当前栈顶的record;如果指定的identifier并不存在于internal compiler stack,则pop操作被忽略; 4. identifier:可选参数;当同push一起使用时,赋予当前被压入栈中的record一个名称;当同pop一起使用时,从internal compiler stack中pop出所有的record直到identifier被pop出,如果identifier没有被找到,则忽略pop操作; 5. n:可选参数;指定packing的数值,以字节为单位;缺省数值是8,合法的数值分别是1、2、4、8、16。 #pragma pack规定的对齐长度,实际使用的规则是:结构,联合或者类的数据成员,第一个放在偏移为0的地方,以后每个数据成员的对齐,按照#pragma pack指定的数值和这个数据成员自身长度中比较小的值来对齐。 也就是说,当#pragma pack的值等于或超过所有数据成员长度的时候,这个值的大小将不产生任何效果。 而结构整体的对齐,则按照结构体中size最大的数据成员和#pragma pack指定值之间较小的值来对齐。 ------------------------------------------- 例1: #pragma pack(2) struct TestA{ public:int a; // 第一个成员,放在[0,3]偏移的位置。char b; // 第二个成员sizeof(char)=1,#pragma pack(2), 取小值也就是1,所以这个成员按1字节对齐,放在偏移[4]的位置。short c; // 第三个成员sizeof(short)=2, #pragma pack(2),取小值也就是2,所以这个成员按2字节对齐,所以放在偏移[6,7]的位置。char d; // 第四个成员sizeof(short)=1, #pragma pack(2), 取小值也就是1,所以这个成员按1字节对齐,放在[8]的位置。}; #pragma pack() struct TestA中size最大的数据成员(4),#pragma pack(2), 取小值也就是2,所以sizeof(TestA)应当按照2来对齐,为10。 ------------------------------------------- 例2: #pragma pack(4) struct TestB{ public:int a; // 第一个成员,放在[0,3]偏移的位置。char b; // 第二个成员sizeof(char)=1,#pragma pack(4), 取小值也就是1,所以这个成员按1字节对齐,放在偏移[4]的位置。short c; // 第三个成员sizeof(short)=2, #pragma pack(4),取小值也就是2,所以这个成员按2字节对齐,所以放在偏移[6,7]的位置。char d; // 第四个成员sizeof(short)=1, #pragma pack(4), 取小值也就是1,所以这个成员按1字节对齐,放在[8]的位置。}; #pragma pack() struct TestB中size最大的数据成员(4),#pragma pack(4), 取小值也就是4,所以sizeof(TestB)应当按照4来对齐,为12。 ------------------------------------------- 例3: #pragma pack(8) struct s1 { short a; // 第一个成员,放在[0, 1]偏移的位置。 long b; // 第二个成员sizeof(long)=4, #pragma pack(8), 取小值也就是4,所以这个成员按4字节对齐,放在偏移[4~7]的位置。 }; struct s2 { char