struct与字节对齐——深入理解

本来想自己总结一下的,后来看到知乎有大牛写的总结非常好,转载之。

https://zhuanlan.zhihu.com/p/210999004

————————————————————————————————————————

**一句话总结:编译器下,全体变量成员进行边界(地址)对齐!**

 

两句话解释:

①编译器,会先让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;

}

```

 

结果如下:

![在这里插入图片描述](https://img-blog.csdnimg.cn/20200901204414784.png#pic_center)

 

附:其他方法取消字节对齐,以下方法也可以

 

#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的倍数。

         所以合理的类型对象声明顺序会,节约内存空间。

         例子:

 

 
  1. typedef struct

  2. {

  3. int a;

  4. int b;

  5. char c;

  6. }s1;


根据对齐限制的要求,

 

s1占用9个字节的空间,但事实并非如此。如果s1类型的对象占9个字节的空间那么,对于对象s1 temp[2]。对象temp[1]地址为9的倍数,其中成员就不满足对齐限制的要求。所以s1类型的数据对象占用的空间是12个字节,以满足在数组类型下地址对齐的要求。

 

                         

在理解了对齐限制后,就明白了结构体所占空间为逻辑不相等的原因。

转载自 https://blog.csdn.net/linmars24/article/details/7852332

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值