
本文共 3639 字,大约阅读时间需要 12 分钟。
本来想自己总结一下的,后来看到知乎有大牛写的总结非常好,转载之。
————————————————————————————————————————
**一句话总结:编译器下,全体变量成员进行边界(地址)对齐!**
两句话解释:
①编译器,会先让struct结构体内部成员按顺序进行边界(存放地址)对齐,对齐标准是各自变量类型的长度;
②然后再让struct结构体进行最后的字节补充(方便下一个变量对齐),对齐标准是结构体内部类型长度最大的变量的整数倍。
1. 名词解释:
①什么叫字节对齐?
其实可以说是边界对齐,实际上就是让变量放置的那个地址要跟4对齐(假设默认情况下为4字节对齐,这个看情况,本人编译器为4字节对齐),或者2对齐等等的。
通俗地讲(不是很严谨):例如我一个变量int a,想放在0x03的地方,这个就不叫对齐,应该放在0x04的地方,这才对齐。因为根据标准,我int类型长度为4(假设,具体情况具体分析),那么,我的地址是4的整数倍,那就没问题了;
②什么叫字节补充?
其实就是对变量后面的地方补充空的内容,可以理解为“占个坑”。
例如,我有一个结构体,最后的地址刚好落在0x010(没有做字节对齐,仅假设),那么,下一int b的变量,他就不能放在0x011的位置了,因为他也要进行字节对齐啊,所以,他得往后挪。因此,为了方便后面的变量,编译器会使这个结构体的末尾的下一个也要做到对齐,那么,没内容的时候,就会给他补充内容,占几个坑,让下一个变量一来就能够自然地字节对齐。
③为什么要字节对齐?
这个很多情况下是为了效率,为了更高的效率。
32bit的系统,每次可以读取32bit,即4个字节,只要一个四字节长度的变量放在这四字节对齐内,就能一次读完(例如放在0x04到0x07内,则系统能一次性读完);
如果这个变量放在0x05到0x08内,那么,系统第一次只能是先读4-7(取5-7的内容),第二次再读8-11(取8的内容),然后,还得把他们组合在一起才行。
这么看来,速率不就是差了很多了?
④字节对齐的隐患:
很多时候不需要考虑,但是,如果对于某些本身没有4对齐的结构体,但是,却要根据某字节的长度进行跳转读取的(使用sizeof),则会出现问题。
例如是在读取bmp图片的文件头时(假设存放地址为0x00),它的结构体长度为14(实际),但是,默认情况下使用sizeof的时候,它就是16了,读取的指针一下子就窜到了0x17,便遗漏了0x15,0x16的内容,同时,0x17后面的内容也全部乱套了。。。。
因此,此时,则需要取消字节对齐,取消方法下面有。
2. 实现过程:
看这个结构体:
typedef struct __test
{
char c_x; //1 byte
float f_y; //4 bytes
} test_type1_t; //此时字节对齐为8
过程:编译器看到第一个变量c_x,存放地址为0x12,它为char类型,长度为1,它的对齐标准就是1,然后,要让它的地址为1的整数倍即可,原地址没问题;对于第二个变量为float类型,长度为4,它的对齐标准就是4,但是,紧挨着的应该是0x14,很明显它不对齐,因此,只能往后挪,即在他前面空了三个位置,然后它的起始地址就是0x16,最后再占4个字节,一直到0x19(下一格为0x20,能够让后面的变量保持字节对齐)。
整个结构体的长度就是0x19-0x12+1 = 8,是本结构体内部类型长度最大的成员float的长度的整数倍。
3. List item
看测试代码:
1.字节对齐与不对齐:
```c
#include <stdio.h>
typedef struct __test
{
char c_x; //1 byte
float f_y; //4 bytes
} test_type1_t; //此时字节对齐为8
typedef struct __test2
{
char c_x; //1 byte
double d_y; //8 bytes
} test_type2_t; //此时字节对齐,为16
typedef struct __test3
{
//char c_x; //1 byte
double d_y; //8 bytes
char c_x;
} test_type3_t; //此时字节对齐,仍然是16
typedef struct __test4
{
char c_x;
double d_y;
} __attribute__((packed)) test_type4_t; //此处已取消字节对齐
```
```c
int main()
{
test_type1_t test1_temp;
test_type2_t test2_temp;
test_type3_t test3_temp;
test_type4_t test4_temp;
printf("the bytes count of 'char + float' is :%d\n",sizeof(test1_temp));
printf("the bytes count of 'char + double' is :%d\n",sizeof(test2_temp));
printf("the bytes count of 'double + char' is :%d\n",sizeof(test3_temp));
printf("the bytes count of 'char + double +attribute(packed)' is :%d\n",sizeof(test4_temp));
return 0;
}
```
结果如下:
![在这里插入图片描述]()
附:其他方法取消字节对齐,以下方法也可以
#pragma pack(1) //此时将进行1字节对齐(即不对齐)
struct test
{
char x1;
short x2;
float x3;
char x4;
};
#pragma pack() //取消1字节对齐,恢复为默认4字节对齐
重复文章开头:
一句话总结:编译器下,全体变量成员进行边界(地址)对齐!
两句话解释:
①编译器,会先让struct结构体内部成员按顺序进行边界(存放地址)对齐,对齐标准是各自变量类型的长度;
②然后再让struct结构体进行最后的字节补充(方便下一个变量对齐),对齐标准是结构体内部类型长度最大的变量的整数倍。
————————————————————————————————————————————————————————————————
补充:
深入理解计算机系统里面也有一些说明:
对齐限制
c语言中在用sizeof()函数判断一个结构体类型(struct)所占字节大小的时候会发现它可能比理论上的所占字节大小要大。这是由于许多计算机系统对基本数据类型的 可允许地址做出了一些限制,要求某种类型的对象的地址必须是某个值k(通常是2,4,8)的倍数,这称为对齐限制。这种限制可以简化处理器和存储器系统之间的接口的硬件设计。
例子,假设一个处理器总是从存储器中取8字节数据,则地址必须为8的倍数。如果将所有的double类型的对象地址对齐成8的倍数,那么用一次的存储器操作就能完成读写操作。
当然无论数据是否对齐,计算机都能正确执行。对象的地址对齐是以空间换时间来提高处理效率。大多数编译器在编译的时候给出了是否对齐的选项。默认是对齐的。
Linux使用的对齐策略是2字节数据类型的地址必须是2的倍数,而较大的数据类型(int,int*,float,double)的地址必须是4的倍数。也就是要求一个short类型的地址的最低位必须等于0.而较大的数据类型的地址最低两位必须都是0.
注意Linux上可以使用命令行-malign-double使GCC为double类型的数据使用8字节的对齐方式,但在与用4字节对齐方式下编译的库代码链接时,会导致不兼容。
Windows对齐方式是,任何k字节基本对象的地址都必须是k的倍数。也就是说int类型的对象地址是4的倍数,double类型的对象地址是8的倍数。
所以合理的类型对象声明顺序会,节约内存空间。
例子:
-
typedef struct
-
{
-
int a;
-
int b;
-
char c;
-
}s1;
s1占用9个字节的空间,但事实并非如此。如果s1类型的对象占9个字节的空间那么,对于对象s1 temp[2]。对象temp[1]地址为9的倍数,其中成员就不满足对齐限制的要求。所以s1类型的数据对象占用的空间是12个字节,以满足在数组类型下地址对齐的要求。
在理解了对齐限制后,就明白了结构体所占空间为逻辑不相等的原因。
转载自
转载地址:https://blog.csdn.net/huohunri2013/article/details/111642980 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!
发表评论
最新留言
关于作者
