语言基础(12):struct/union字节对齐

摘要:
不同的成员类型typedefstructnode1{inta;charb;shortc;}S1;则sizeof=8。结构体嵌套typedefstructnode4{boola;S1s1;//由前可知4字节对齐shortb;}S4;则sizeof=16。是因为s1占8字节,而s1中最长数据类型为int,占4字节,而double占8字节,因此结构体以8字节对齐,则存放方式为:使用了#pragmapack若需取消强制对齐方式,则可用命令#pragmapack()如在程序开头使用命令#pragmapack,对于下面的结构体typedefstructnode5{boola;S1s1;doubleb;intc;}S5;则sizeof=24.因为强制以4字节对齐,而S5中最长数据类型为double,占8字节,因此结构体以4字节对齐。

1、什么是字节对齐

在用sizeof运算符求算某结构体所占空间时,并不是简单地将结构体中所有元素各自占的空间相加,这里涉及到内存字节对齐的问题。从理论上讲,对于任何变量的访问都可以从任何地址开始访问,但实际上访问特定类型的变量只能在特定的地址访问,这就需要各个变量在空间上按一定的规则排列, 而不是简单地顺序排列,这就是内存对齐。

2、为什么要字节对齐

  1. 某些平台只能在特定的地址处访问特定类型的数据;
  2. 提高存取数据的速度。比如有的平台每次都是从偶地址处读取数据,对于一个int型的变量,若从偶地址单元处存放,则只需一个读取周期即可读取该变量;但是若从奇地址单元处存放,则需要2个读取周期读取该变量。
    根本原因在于CPU访问数据的效率问题。假设上面整型变量的地址不是自然对齐,比如为0x00000002,则CPU如果取它的值的话需要访问两次内存,第一次取从0x00000002-0x00000003的一个short,第二次取从0x00000004-0x00000005的一个short然后组合得到所要的数据,如果变量在0x00000003地址上的话则要访问三次内存,第一次为char,第二次为short,第三次为char,然后组合得到整型数据。而如果变量在自然对齐位置上,则只要一次就可以取出数据。一些系统对对齐要求非常严格,比如sparc系统,如果取未对齐的数据会发生错误,举个例:
	char ch[8];
	char *p = &ch[1];
	int i = *(int *)p;

运行时会报segment error,而在x86上就不会出现错误,只是效率下降。

3、字节对齐策略

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
先让我们看四个重要的基本概念:
a. 数据类型自身的对齐值:对于char型数据,其自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,单位字节;
b. 结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值;
c. 指定对齐值:#pragma pack (value)或__attribute__((aligned(value))) 时的指定对齐值value;
d. 数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中小的那个值;
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
字节对齐策略如下:

  1. 数据成员自身对齐,对齐值为:min{ 类型自身对齐值,设定值 };
  2. 结构体或类对齐,对齐值为:min{ max{成员对齐值},设定值 };

PS:由于在x86下,GCC默认按4字节对齐

4、字节对齐实例

下面看一下sizeof在计算结构体大小的时候具体是怎样计算的
(1) 空结构体

typedef structnode
{

}S;

则sizeof(S)=1;或sizeof(S)=0;
在C++中占1字节,而在C中占0字节。

(2)不同的成员类型

typedef structnode1
{
inta;
charb;
shortc;
}S1;

则sizeof(S1)=8。这是因为结构体node1中最长的数据类型是int,占4个字节,因此结构体以4字节对齐,则该结构体的内存结构(黄色为补齐字节,绿色为占用字节,一个空格表示一个字节):
语言基础(12):struct/union字节对齐第1张
总共占8字节

(3)成员不同顺序

typedef structnode2
{
chara;
intb;
shortc;
}S2;

则siezof(S3)=12.最长数据类型为int,占4个字节。因此结构体以4字节对齐,内存空间存放方式:
语言基础(12):struct/union字节对齐第2张
总共占12个字节

(4)含有静态数据成员

typedef structnode3
{
inta;
shortb;
staticintc;
}S3;

则sizeof(S3)=8.这里结构体中包含静态数据成员,而静态数据成员的存放位置与结构体实例的存储地址无关(注意只有在C++中结构体中才能含有静态数据成员,而C中结构体中是不允许含有静态数据成员的)。其在内存中存储方式如下:
语言基础(12):struct/union字节对齐第3张
总共占用8个字节,变量c是单独存放在静态数据区的,因此用siezof计算其大小时没有将c所占的空间计算进来。

(5) 结构体嵌套

typedef structnode4
{
boola;
S1 s1;   // 由前可知4字节对齐
shortb;
}S4;

则sizeof(S4)=16。是因为s1占8字节,而s1中最长数据类型为int,占4个字节,bool类型1个字节,short占2字节,因此结构体以4字节对齐,则存储方式为
语言基础(12):struct/union字节对齐第4张

(6)更复杂成员

typedef structnode5
{
boola;
S1 s1;   // 由前可知4字节对齐
doubleb;
intc;
}S5;

则sizeof(S5)=32。是因为s1占8字节,而s1中最长数据类型为int,占4字节,而double占8字节,因此结构体以8字节对齐,则存放方式为:
语言基础(12):struct/union字节对齐第5张

(7)使用了#pragma pack(n)
若需取消强制对齐方式,则可用命令#pragma pack()
如在程序开头使用命令#pragma pack(4),对于下面的结构体

typedef structnode5
{
boola;
S1 s1;
doubleb;
intc;
}S5;

则sizeof(S5)=24.因为强制以4字节对齐,而S5中最长数据类型为double,占8字节,因此结构体以4字节对齐。在内存中存放方式为:
语言基础(12):struct/union字节对齐第6张

(8)union 类型 内存对齐

union DATE
{
    char a;
    int i[5];
    double b;
};
DATE max;
cout<< sizeof(max) << endl;

这个问题很好回答,并且我把这个问题归结于基本概念题(就是入门书必须介绍的)。我想一般来说,做过内存管理的,对这个语言特性肯定不会陌生。

摘几句The C Programming Language里面讲述这个问题的原话,以说明读书还是必要的:

①联合就是一个结构;
②它的所有成员相对于基地址的偏移量都为0;
③此结构空间要大到足够容纳最“宽”的成员;
④并且,其对齐方式要适合于联合中所有类型的成员。

该结构要放得下int i[5]必须要至少占4×5=20个字节。如果没有double的话20个字节够用了,此时按4字节对齐。但是加入了double就必须考虑double的对齐方式,double是按照8字节对齐的,所以必须添加4个字节使其满足8×3=24,也就是必须也是8的倍数,这样一来就出来了24这个数字。综上所述,最终联合体的最小的size也要是所包含的所有类型的基本长度的最小公倍数才行。

PS:联合的使用
联合在存储分配的时候用很多,因为很少有像存储分配这样需要给多种不同类型的变量分配空间而又打算尽可能的节约内存的,这很适合联合的特性。上述对齐的方式有个很有趣的用法也就常在存储分配里面使用。(下面依旧用The C Programming Language中的例子作答)

typedef long Align;
union header {
    struct {
        union header *ptr;
        unsigned size;
    } s;
    Align x;
}

这里的Align有什么用?作用只有一个,就是强迫分配的结构体按long的长度对齐。

PS:对齐是低地址到高地址对齐的,如下:

union DATE
{
 char a;
 int i[5];
 double b;
};

DATE max;
max.a = 0x12;
printf(“0x%X/n”,max.i[0]);
printf(“0x%X/n”,max.i[1]);

输出为: 
0xCCCCCC12
0xCCCCCCCC

5、reference

http://www.360doc.com/content/16/0510/14/7510008_557849893.shtml
https://blog.csdn.net/geekdonie/article/details/13627257
https://i.cnblogs.com/EditPosts.aspx?postid=11553650

免责声明:文章转载自《语言基础(12):struct/union字节对齐》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇联通积分兑换的Q币怎么兑换到QQ上https证书设置以及设置301跳转下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

[学习]安装记录: 御剑 burp

一、御剑 御剑是用来IP扫描的,看服务器在哪个ip上,可以扫出后台地址,检测注入漏洞,功能很多 C段速度超级快,实际测试扫描一个C段的绑定域名只要几秒 下载地址,百度搜了一下1.5 直接在这里就可以:http://www.exehack.net/775.html 然后,开始扫描,啊,我还替换了一下大字典的文件,啊,我简直是个傻子。 我不知道怎么添加,为此...

上位机开发之三菱Q系列PLC通信实践

经常关注我们公众号或者公开课的学员(如果还没有关注的话,左上角点击一波关注)应该知道,我们会经常使用西门子PLC,其实对于其他品牌的PLC,我们都会讲到,包括三菱、欧姆龙、基恩士、松下及国产台达、信捷等,之所以使用西门子PLC为例,一方面是因为学员当中使用西门子PLC居多,而且西门子的市场占有率也比较高,再者,我觉得做上位机开发,其实对PLC品牌并不太注重...

shell 脚本 常用命令

Shell 脚本常用命令  Shell脚本是Linux开发工作中常用的工具,但是我一直没有找到一个适合自己的简明扼要的HandBook。在工作过程中整理了一下,贴在这里已备查看。 1           Shell中的特殊符号 1.1           $  美元符号。用来表示变量的值。如变量NAME的值为Mike,则使用$NAME就可以得到“Mike...

jmeter 跨线程组调用变量

由于有些特殊需求需要在线程组之间调用变量,这里就总结一下几种常用方法(只是个人会的)。 在使用时要注意线程组的执行顺序,需要勾选测试计划里的独立运行每个线程组按钮。还需要注意设置全局变量的后置处理器应该是在第一个线程组(即提取局部变量的那个线程组)。 一、beanshell自带方法 props.put("变量名","值") 全局变量赋值 props.ge...

四个有害的Java编码习惯

    对编程语言而言,好的编码风格不仅能在程序编写初期生成有效的框架编码,还可以让我们的编码更加清晰规范。但是,正如本文作者所说,一些Java程序的编码风格虽应用广泛,却会对编码的可维护性产生负面影响,对我们的编程有害。本文告诉你如何打破这种风格,重写这4个有害的编码风格,优化编码,提高可维护性。 程序中的编码风格让我们的编程工作变得轻松,特别是程序维护员...

ubuntu建立文件或者文件夹软链接

文件夹建立软链接(用绝对地址)   ln -s 源地址  目的地址   比如我把linux文件系统rootfs_dir软链接到/home/jyg/目录下   ln -s /opt/linux/rootfs_dir  /home/jyg/rootfs_dir就可以了 删除软连接: rm -rf 目的地址 比如:rm -rf /home/jyg/rootfs_...