最近写软件的时候,结构体对齐的问题困扰了一段时间,收集一些资料,供大家参考:
数据对齐
自然对齐:如果一个变量的内存地址正好是它长度的整倍数,它就被称为自然对齐。一些体系结构对对齐要求非常严格。通常想基于RISC的系统, 载入未对齐数据会导致处理器陷入(一种可处理的错误)。还有一些系统可以访问没有对齐的数据,只不过性能会下降。编写可移植性高的代码要避免对齐问题,保证所有的类型都能够自然对齐。
避免对齐引发的问题
一个数据类型长度比较小,它本来是对齐的,如果你用一个指针进行类型转换,并且转换后的数据类型较长,那么通过改指针进行数据访问时就会引发对齐问题。也就是说,下面的代码是错误的:
char dob[10];
char *p = &dog[1];
unsigned long l = *(unsigned long *)p;
这个例子将一个指向char型的指针当作指向unsigned long型的指针来用,这会引起问 题,因为此时会试图从一个并不能被4整除的内存地址上载入32的unsigned long型数据。
非标准类型的对齐
前面提到了,对于标准数据类型来说,它的地址只要是其长度的整数倍就对齐了。而非标准的(复合的)C数据类型按照下列原则对齐:
1〉对于数组,只要按照基本数据类型进行对齐就可以了(其后的所有元素自然都能够对齐了)。
2〉对于联合,只要它包含的长度最大的数据类型能够对齐就可以了。
3〉对于结构体,只要它包含的长度最大的数据类型能够对齐就可以了。
结构体还要引入 填补机制,这会引发下一个问题。
结构体填补
为了保证结构体中每一个成员都能够自然对齐,结构体需要被填补。举例:
上面的代码中对于结构体 BBB 的定义如下:
struct BBB{
char aChar; // 1 byte
double aDouble; // 8 bytes
int aInt; // 4 bytes
};
由于该结构体不能准确地满足各个成员自然对齐,所以它在内存中可不是按照原样存放的。编译器会在内存中创建一个类似下面的给出的结构体:
struct BBB{
char aChar; // 1 byte
u8 __pad0[7]; // 7 bytes
double aDouble; // 8 bytes
int aInt; // 4 bytes
u8 __pad1[4]; // 4 bytes
};
填补的变量都是为了让数据自然对齐而加入的。__pad0 是为了让 aDouble 能够自然对齐而加入的,而其他额外的填补如 __pad1 则是为了让结构体的长度能够被最大元素(aDouble)的长度 8 整除而加入的。结构体AAA的长度为16,而不是13就是因为后一种填补引起的。通常你可以通过重新排列结构中的对象来避免填充或减少填充。像结构体AAA那样,把元素按长度的大小递减/增的排列后就可以使它占最小的空间了。
注意:编译器,优化器并不能改变结构体中元素的排列次序。如ANSI C就明确规定不允 许编译器改变结构体内成员对象的次序,它总是由你--程序员来决定。 并不是所有的结构体进行这样的调整的,
比如:该结构体作为一个标准的一部分,或者 它是现有代码的一部分。