工作中面试中对于字节对齐基本上是必考一个知识点,而很多面试是网络上上原题。基本上背一背就可以写正确,而关于4字节对齐我相信很多人也只是一个基本地了解,对于一些题目就感觉有问题,而且很多blog后面仍然有很多人在问一些题目,说明该blog并没有讲清楚这个问题。然后我自己也有疑问,所以就找了一些更多资料,终于是解了。

比如说

对于32位机器

1 #pragma pack(4) 
2 struct a{short a;short b;short c;} ;
3 sizeof(a)==6 //为什么不为8

不是说按4字节对齐那么最后面的一个short应该填充2字节的padding吗?该问题在百度知道上有人提问,问到最后我估计提问者也是花好久才可能明白,或者后面也没有明白

百度提问地址:http://zhidao.baidu.com/link?url=SVyAiSChbzcvTNIWuQP8RbfleHxTYJPgtSbGtpm5dOQM3M7FHZzqy0UVmqL5zNOuO2XeELgGbg7cafRuIs5_Za

引用CSDN上一个讲解字节对齐评论中的提问:

大神,我发现有点小问题,可能是我理解不当

1 struct A{
2 char a;
3 double b;
4 int c;
5 char d;
6 };//sizeof(A) = 24

如果按照规矩来这个明显要按照8字节对齐,

可为什么32位计算机还是要按照8字节对齐?显然我按照4字节对齐,总线访问效率更高呀?我觉得为什么字节对齐不受制于总线长度?望不吝赐教,小生在此拜谢

该Blog地址:http://blog.csdn.net/21aspnet/article/details/6729724

总体来讲要解决上述问题应该了解不仅仅是4字节对齐这么简单地去记忆,应该按照

对于结构体,在使用sizeof的时候会进行字节的对齐,对齐的规则如下: 

1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除; 

   备注:编译器在给结构体开辟空间时,首先找到结构体中最宽的基本数据类型,然后寻找内存地址能被该基本数据类型所整除的位置,作为结构体的首地址。 

2) 结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding); 

  备注:为结构体的一个成员开辟空间之前,编译器首先检查预开辟空间的首地址相对于结构体首地址的偏移是否是本成员的整数倍,若是,则存放本成员,反之,则在本成员和上一个成员之间填充一定的字节,以达到整数倍的要求,也就是将预开辟空间的首地址后移几个字节。 

3)结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要,编译器会在最末一个成员之后加上填充字节(trailing padding)。 

  备注:结构体总大小是包括填充字节,最后一个成员满足上面两条以外,还必须满足第三条,否则就必须在最后填充几个字节以达到本条要求。

以上3条要认真学习就知道要对于结构体而讲要注意最宽的基本数据类型,还有每个成员对于首地址的offset是成员大小的整数倍就应该明白

struct
{
char a;
double b;
}A;//sizeof(A)=16

而不是单单记在常考的32位机下4字节对齐这么简单。

当然最后也推荐一个自己认为讲得很清楚地BLOG,我怕内容有删除,就转载过来。地址为http://blog.csdn.net/nellson/article/details/5293588

对于结构体,在使用sizeof的时候会进行字节的对齐,对齐的规则如下: 

1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除; 

   备注:编译器在给结构体开辟空间时,首先找到结构体中最宽的基本数据类型,然后寻找内存地址能被该基本数据类型所整除的位置,作为结构体的首地址。 

2) 结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding); 

  备注:为结构体的一个成员开辟空间之前,编译器首先检查预开辟空间的首地址相对于结构体首地址的偏移是否是本成员的整数倍,若是,则存放本成员,反之,则在本成员和上一个成员之间填充一定的字节,以达到整数倍的要求,也就是将预开辟空间的首地址后移几个字节。 

3)结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要,编译器会在最末一个成员之后加上填充字节(trailing padding)。 

  备注:结构体总大小是包括填充字节,最后一个成员满足上面两条以外,还必须满足第三条,否则就必须在最后填充几个字节以达到本条要求。 

按照上面的规则,对如下几个例子进行分析: 

例1: 



1struct
DATA1 

2

3    char    c1;
//偏移量0,累积size = 1 

4    char    c2;
//偏移量1,累积size = 1 + 1 = 2 

5    short    si;
//偏移量2,累积size = 2 + 2 

6}; 



这个例子中,首先找到结构体变量的首地址,按照规则1,该首地址需要能被最宽基本类型成员整除,即能被short(字节数2)整除。接下来,为c1分配内存,由于其占一个字节,此时内存状态为*,然后,为c2分配内存,由于结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,而c2是char型成员,占用一个字节,刚好能够对齐,此时内存状态为**,最后,为short型成员分配内存,由于前两个成员占2个字节,si相对于结构体首地址的偏移量刚好是2的整数倍,所以,此时内存状态为**** 



例2: 

1struct
DATA2 

2

3    char    c1;
//偏移量0,累积size = 1 

4    short    si;
//偏移量1 + (1),累积size = 1 + (1) + 2 = 4 

5    char    c2;
//偏移量4,累积size = 4 + 1 = 5,但按最大长度sizeof(short) = 2对齐,故最后取6 

6}; 



这个例子中,首先找到结构体变量的首地址,与上例同。接下来,为c1分配内存,由于其占一个字节,此时内存状态为*,然后,为si分配内存,由于结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,而si是short型成员,占用2个字节,而前一个成员占用一个字节,所以需要在c1与si之间补一个字节进行对齐,此时内存状态为*×**,最后,为char型成员分配内存,由于前两个成员占4个字节,c2相对于结构体首地址的偏移量是1的整数倍,所以,此时内存状态为*×***,接下来,按照规则3,结构体的总大小为结构体最宽基本类型成员大小的整数倍,所以总大小应该是short(2字节)型变量大小的整数倍,需要再补一个字节,即*×***×,总共占6个字节 



例3: 

1struct
DATA3 

2

3    char    c1;
//偏移量0,累积size = 1 

4    double    d;
//偏移量1 + (7),累积size = 1 + (7) + 8 = 16 

5    char    c2;
//偏移量16,累积size = 16 + 1 = 17,但按最大长度sizeof(double) = 8对齐,故最后取24 

6}; 





关于#pragma pack(n) 

VC中提供了#pragma pack(n)来设定变量以n字节对齐方式。n字节对齐就是说变量存放的起始地址的偏移量有两种情况:第一、如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式,第二、如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。结构的总大小也有个约束条件,分下面两种情况:如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数;否则必须为n的倍数。 

例4: 

1#pragma
pack(push) //保存对齐状态 

2#pragma
pack(2)//设定为2字节对齐 

3struct
DATA4 

4

5    char    c1; 

6    double  d1; 

7    int    i1; 

8    short  s1; 

9}; 

10#pragma
pack(pop)//恢复对齐状态 

以上结构的大小为16,下面分析其存储情况,首先为c1分配空间,其偏移量为0,c1占用1个字节,由于1<2,所以按照char类型变量默认的对其方式。接着开始为d分配空间,这时其偏移量为1,需要补足1个字节,这样使偏移量满足为n=2的倍数(因为sizeof(double)大于n),d占用8个字节。接着为i1分配空间,这时其偏移量为10,满足为2的倍数,i1占用4个字节。这时已经为所有成员变量分配了空间,共分配了14个字节。 接下来再为s1分配空间,由于min(sizeof(s1),2)=2,所以s1按照2字节对齐,此时已经是对其的,所以分配2字节内存,这时,总共占用16字节 



例5: 

1#pragma
pack(push) //保存对齐状态 

2#pragma
pack(4)//设定为4字节对齐 

3struct
DATA4 

4

5    char    c1; 

6    double  d; 

7    int    i1; 

8    short  s1; 

9}; 

10#pragma
pack(pop)//恢复对齐状态 

以上结构的大小为20,下面分析其存储情况,首先为c1分配空间,其偏移量为0,c1占用1个字节,由于1<2,所以按照char类型变量默认的对其方式。接着开始为d分配空间,这时其偏移量为1,需要补足3个字节,这样使偏移量满足为n=4的倍数(因为sizeof(double)大于n),d占用8个字节。接着为i1分配空间,这时其偏移量为12,满足为4的倍数,i1占用4个字节。这时已经为所有成员变量分配了空间,共分配了16个字节。接下来再为s1分配空间,由于min(sizeof(s1),4)=2,所以s1按照2字节对齐,此时已经是对齐的,所以分配2字节内存,这时,总共占用16字节,由于占用内存总数需要为4的整数倍,所以结构大小为20 



【参考文献】 

1.《对几组sizeof信息的分析》 http://blog.vckbase.com/billdavid/archive/2004/06/23/509.html 

2.《sizeof(结构体)和内存对齐http://www.ksarea.com/articles/20071004_sizeof-struct-memory.html

关于sizeof与#pragma pack 以及网络上关于字节对齐的一点感想的更多相关文章

  1. stm32中使用#pragma pack(非常有用的字节对齐用法说明)

    #pragma pack(4)   //按4字节对齐,但实际上由于结构体中单个成员的最大占用字节数为2字节,因此实际还是按2字节对齐 typedef struct { char buf[3];//bu ...

  2. pragma pack(非常有用的字节对齐用法说明)

    强调一点: #pragma pack(4) typedef struct { char buf[3]; word a; }kk; #pragma pack() 对齐的原则是min(sizeof(wor ...

  3. #Pragma Pack与内存分配

    博客转载自:https://blog.csdn.net/mylinx/article/details/7007309 #pragma pack(n) 解释一: 每个特定平台上的编译器都有自己的默认“对 ...

  4. #pragma pack(push,1)与#pragma pack(1)的区别

    这是给编译器用的参数设置,有关结构体字节对齐方式设置, #pragma pack是指定数据在内存中的对齐方式. #pragma pack (n)             作用:C编译器将按照n个字节对 ...

  5. C语言字节对齐 __align(),__attribute((aligned (n))),#pragma pack(n)

    转载地址 : http://blog.csdn.net/21aspnet/article/details/6729724 一.概念    对齐跟数据在内存中的位置有关.如果一个变量的内存地址正好位于它 ...

  6. (转载)关于#pragma pack(push,1)和#pragma pack(1)

    转载http://www.rosoo.net/a/201203/15889.html 一.#pragma pack(push,1)与#pragma pack(1)的区别 这是给编译器用的参数设置,有关 ...

  7. sizeof,终极无惑(上)

    0. 前向声明 sizeof,一个其貌不扬的家伙,引无数菜鸟竟折腰,小虾我当初也没少犯迷糊,秉着“辛苦我一个,幸福千万人”的伟大思想,我决定将其尽可能具体的总结一下. 但当我总结的时候才发现,这个问题 ...

  8. #Pragma Pack(n)与内存分配

    #pragma pack(n) 解释一: 每个特定平台上的编译器都有自己的默认"对齐系数"(也叫对齐模数).程序员可以通过预编译命令#pragma pack(n),n=1,2,4, ...

  9. 【转】#pragma pack(push,1)与#pragma pack(1)的区别

    这是给编译器用的参数设置,有关结构体字节对齐方式设置, #pragma pack是指定数据在内存中的对齐方式. #pragma pack (n)             作用:C编译器将按照n个字节对 ...

随机推荐

  1. cryptsetup文件系统加密

    今天做了SYC攻防题的文件系统挂载部分,在找到挂载最内层的final文件时发现mount无法识别,这也许就是一个加密的文件系统吧,还好-在龟速的 网络环境下查阅到了losetup循环挂载系统命令,但是 ...

  2. undefined reference to symbol '_ZNK11GenICam_3_016GenericException17GetSourceFileNameEv'

    今天在编译DALSA二次开发的源码时,出现了如下错误: /usr/bin/ld: ./out/camera.o: undefined reference to symbol '_ZNK11GenICa ...

  3. MySQL--详细查询操作(单表记录查询、多表记录查询(连表查询)、子查询)

    一.单表查询 1.完整的语法顺序(可以不写完整,其次顺序要对) (不分组,且当前表使用聚合函数: 当前表为一组,显示统计结果 ) select distinct [*,查询字段1,查询字段2,表达式, ...

  4. nginx中Geoip_module模块的使用

    nginx中Geoip_module模块的使用 .安装模块,nginx也是通过yum安装 yum install nginx-module-geoip -y # 可以看到模块的链接库文件 [root@ ...

  5. 一种简单的生产环境部署Node.js程序方法

    最近在部署Node.js程序时,写了段简单的脚本,发觉还挺简单的,忍不住想与大家分享. 配置文件 首先,本地测试环境和生产环境的数据库连接这些配置信息是不一样的,需要将其分开为两个文件存储 到conf ...

  6. Django makemigrations 不行时

  7. Navicat系列产品激活教程

    准备 本教程可破解12.x版本,如果教程失效请联系我 # 19.1.11 破解暂时失效,请勿更新 (如已更新请卸载重新安装老版本,数据不会丢失 http://download.navicat.com/ ...

  8. linux与window互传文件

    首先我们的目标是把一个很大的文件传到linux中,所以我们先要让linux节点开启ftp服务 FTP  10.1.61.212 如果对方没有开启FTP服务,那么这个命令就不会有效果. 1.开启xftp ...

  9. 一个完整Java Web项目背后的密码

    前言 最近自己做了几个Java Web项目,有公司的商业项目,也有个人做着玩的小项目,写篇文章记录总结一下收获,列举出在做项目的整个过程中,所需要用到的技能和知识点,带给还没有真正接触过完整Java ...

  10. Confluence 6 配置服务器基础地址

    服务器基础地址(Server Base URL)是用户访问 Confluence 的 URL 地址.这个基础的 URL 地址必须与你在浏览器中访问 Confluence 中的地址. Confluenc ...