adinxu
by adinxu
~1 分钟 阅读用时

分类

标签

内存对齐与字节序

整理下内存对齐和字节序的相关问题,原先整理的都丢了。。。

一、内存对齐

内存对齐是什么?当在c语言中定义复合数据类型,如数组,枚举,联合,结构体等,类型中的成员往往不止一个,而数据定义好之后,数据在内存中的排列就是如定义的那般,一个成员挨着一个成员,连续存放的吗?
答案是否定的。复合数据类型在内存中存储时,成员之间可能存在一些空洞(又名padding),你可以用sizeof查看一些结构,计算原来成员总和,有些结构的实际大小就比总和大一些。在一些对数据排列要求严格的场景,如网络上传输的报文,因其内在定义都是严格规定的,若还是存在空洞,那么数据肯定就出错了。这时一般使用#pragma pack (1),避免空洞出现。
那么为什么会有空洞存在呢?
空洞的存在应该是一种用空间换时间的手段,这要涉及到对内存中数据的访问方式,为了提高cpu读取效率,就出现了内存对齐:如果不是内存对齐的,本来一次可以取完的数据可能需要取两次。
32位cpu上默认的指定对齐值是4字节,64位cpu上默认的指定对齐值是8字节。
那么内存对齐是如何对齐的?默认对齐值有什么用?指定的对齐值又有什么用?
这里有几个概念
默认对齐值:基本类型自身默认对齐值,为自身大小。复合类型默认对齐值:所有成员中自身对齐值最大的那个成员的对齐值(联合和结构体是成员中最大对齐值成员的对齐值)
指定对齐值:#pragma pack(n) 若没指定,那么这里看平台的默认指定对齐值(这个有错误!!详情请看下面引用)
有效对齐值:取指定对齐值(没指定则为平台默认指定对齐值)和自身默认对齐值小的那个。

所有类型(int,long,struct)等都需要保证自己的地址是其有效对齐值的整数倍。另外复合类型除了保证其成员对齐所加的padding外,还有可能在其末尾添加padding,用来保证结构体大小是其有效对齐值的整数倍。这个是为了复合类型数组所考虑的,数组无对齐这一说,所以假如结构体末尾不填充,结构体数组的第二个成员就不能对齐了。结构体的地址其实是对所有类型都满足对齐的,所以就只需要满足结构体中成员的偏移量是有效对齐值的整数倍就可以了。

规则1 数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0
的地方,以后每个数据成员的对齐按照#pragma pack 指定的数值和这个数据成员自身长度中,比较小的那个进行。 规则2 结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack
指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。 规则3 结合1、2 可推断:当#pragma pack 的n 值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。


这段是后续加的,因为遇到了对齐问题,结果被坑了,按照上面的规则,32位最大对齐值为4,但实际不是,找到了如下文章,说的挺好的,建议可以去看一下。
纸上得来终觉浅,绝知此事要躬行

这个大佬大概说了这些:
首先肯定的是 linux gcc 没有默认对齐数。 如果他们设置了默认对齐数,可能会对其他编译器产生不兼容的问题。
其他的系统要求的对齐可能不相同,所以他们在 mingw-gcc on windows 做了一些改变。
最后就是轻喷了一下CSDN的小伙伴们,意思是让我们以后写博客要多测试测试一些数据,不要就测试几组就相信一些结论,然后让别人接收错误的信息。
———————————————— 版权声明:本文为CSDN博主「Gerald Kwok」的原创文章,遵循CC 4.0
BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_42678507/article/details/90109173


之前遇到过一个问题,我想用#pragma pack(1)来消除空洞,用来避免显式声明padding,然后别人告诉我最好不要用#pragma pack(1),说可能会出问题,我后来搜了一下才知道这个不能随便用,这里引用一下别人的描述:

各个硬件平台对存储空间的处理上有很大的不同,一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐。比如sparc系统,如果取未对齐的数据会发生错误,举个例: char ch[8]; char *p = &ch[1]; int i = *(int *)p; 运行时会报segment
error,而在x86上就不会出现错误,只是效率下降。
————————————————
版权声明:本文为CSDN博主「OopspoO」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/cclethe/article/details/79659590

二、字节序

字节序,即超过1字节的其他基本类型的字节排列顺序,比如short in long等,他们的数据在内存中存放的空间超过一字节,排列顺序就是字节序。这里有两种排列顺序,即数据高位存放在内存中的低字节(网络序/大端),和数据低位存放在内存中的低字节(主机序/小端)。
比如一个int类型的数据,它的值为0x01020304,在不同的cpu架构上,它在内存中存储的顺序可能是不同的。在大端设备上为:
**内存地址递增方向->**

0x1|0x2|0x3|0x4

而在小端设备上:

0x4|0x3|0x2|0x1

一般来说,为了字节序不同的设备间通信的兼容,数据在发送前都要转换为网络序,在接收后按需决定是否需要转换。大端设备发送接收都不需要转换字节序,而小端设备发送接收都要转换。而主机转网络和网络转主机序的函数其实作用是相同的。
超过一字节的数据在涉及通信(或存储)时,为了保证一致性,都需要转字节序。字符串数组不需要转字节序,而别的比如int型数组则需要转字节序。
举例,比如接收并存储uchar szxxx[3]和int aixxx[3],在内存中,存储后是这样的。
**内存地址递增方向->**
发送前(远端设备 大端)

uchar szxxx[3] = {1,2,3};
0x1
0x2 0x3
int aixxx[3] = {1,2,3};
0x1
0x0 0x0 0x0 0x2 0x0 0x0 0x0 0x3 0x0 0x0 0x0
接收后(本端设备 小端)
uchar szxxx[3] = {1,2,3};
0x1
0x2 0x3
int aixxx[3] = {1,2,3};
0x1
0x0 0x0 0x0 0x2 0x0 0x0 0x0 0x3 0x0 0x0 0x0
转字节序后(本端设备 小端)
uchar szxxx[3] = {1,2,3};
0x1
0x2 0x3
int aixxx[3] = {1,2,3};
0x0
0x0 0x0 0x1 0x0 0x0 0x0 0x2 0x0 0x0 0x0 0x3