C结构体对齐

本文详细解析了在不同编译器环境下(如MSVS2005和GCC3.4.3)结构体成员对齐的规则及其实现原理,并探讨了如何优化结构体定义以节省内存。

因为我看C++对象模型的时候,遇到了几个内存布局都是有关于对齐的一些细节,故此对结构体对齐再做一份小结,有人说:结构体对齐这个东西是依赖于编译器的,因此不用去研究,真的嘛?

 

也许是,也许不是,要看你是做那个行业的了,如果你是做系统地层,网络通讯,嵌入式系统的,一个字节的节省,也许对你是很大的期望呢。虽然具体的对齐方式是因编译器而异,但是对齐的基本原理是不变的,那个原理也许能指导我们编写程序的时候按照某个原则去进行。

 

不过,既然你用到了C或者C++,就多数是和系统底层有缘之人了,你说是嘛?呵呵。

 

现在先说在windows x86 32位机子下的MS vs2005编译器(就是:cl.exe for x86)下的对齐规则:

 

比如以下的结构体定义:

 

struct A

{

    int i;

    double d;

    char c;

};

 

sizeofstruct A)在vs2005的大小?

 

现在说一下cl.exe(就是微软vs2005的编译器进程)在默认情况下是怎么做的:

 

1、对齐量的确定:找到A中最大的基本类型成员的大小,在本例中是8(double的大小).

 

2、当定义一个结构体变量struct A aA; 的时候,aA的起始地址要被由1确定的对齐量整除,在这个例子里,aA的起始地址一定要能被8整除)

 

2、然后开始分配int i4字节的空间;再分配double d;注意double 8字节,所以要分配在被8整除的地方,因此int  i后面空了4字节填充;

 

3、然后,分配char c1字节,这个时候struct A的大小是4(这是int i;的) + 4 (这是填充的) + 8(这是double d;的) + 1 (这是char c;的)= 17

 

4、最后,要求结构体总的大小要能被由1确定的对齐量整除,在这里是说struct A的大小要能被8整除,所以还要加上7字节的填充字符,一共是24字节。

 

从这里可以看出,vs2005的编译器的结构体填充有这样的规则(默认情况下,这个默认情况可以通过工程属性上面的选项修改):

 

1、对齐量的确定:结构体中最大的数据成员的字节数

 

2、当定义一个结构体变量的时候,起始地址一定能被确定的对齐量整除;

 

2、分配每一个成员的时候,该成员相对于起始地址的偏移(offset)要能被该成员的大小整除;

 

3、结构体总的大小能被确定的对齐量整除;

 

 

 

扩充的字节叫做pad(填充字节)

 

 

下面看linux g++ 3.4.3编译器在x86 32位机子下的默认规则,那相对简单一些:(相当于VS2005中将对齐情况改成4字节对齐)

 

对齐的方式默认是4字节,所以每个成员都按照4字节方式对齐即可。上面的结构体的大小就是:

 

4 (int i;的大小) + 8double 的大小) + 1char 的大小) + 3 (为了4字节填充而补充的字节) = 16字节;

 

至于gcc的结构体变量的首地址分配的特征一时也找不到,还望各位多多指教。谢谢。

 

最后,如果定义一个没有任何成员的结构体,struct A{}; 该大小是多少呢?是1字节。如果定义了两个结构体变量struct A a1, a2; 1字节的填充能够使a1a2的地址区分开来!

 

还有其他的编译器和操作系统就不是我所能知的了。

 

那么,这些规则也许不尽相同,但是给了我们一个编程时候的注意点,就是:

 

定义结构体的时候,成员最好能从大到小来定义,那样能相对的省空间。(至少不会比别的顺序差,一般情况下哈。)

 

比如以上的结构体,如果能这样定义:

 

struct A

{

    double d;

    int i;

    char c;

};

 

那么,无论是windows下的vs2005,还是linux下的gcc,都是16字节。

 

对齐情况还可以通过各个编译器给出的特性在代码中改,不过我没用过,就没有发言权,各位又需要可以参考别的文章。

 

以上的过程和推论是我看别的文章和上机试验的结果,如有不对之处,请各位指教,谢谢。

 

 

再说一点,对于有嵌套的情况,vs2005下的对齐我也不大确定,不过我的分析如下:

 

struct A

{

    char c;

    double d;

    int i;

};

 

struct B

{

    struct  A a;

    char c;

};

 

这种情况下,分析B的大小;

 

1、对齐量的确定:包括A中的各成员比较的,递归下去,比较各个基本类型成员的大小,去最大值,为8(double的大小);

 

2、起始地址和上面的一样,要求B的变量的其实地址能被8整除;

 

3、关于B中struct A a;的偏移和大小和将a单独作为结构体变量的时候一样(那样方便赋值运算)

 

剩下的和上面的规则相同;

 

所以:B的大小就是24(struct A)+ 1(char) +7(pad) = 32字节;

 

不知正确与否?还请诸位评判。

### C语言结构体对齐的原理和实现方式 #### 一、结构体对齐的基本概念 在C语言中,为了提高数据访问效率以及满足硬件架构的要求,编译器会对结构体中的成员进行字节对齐处理。这种对齐遵循一定的规则,主要包括以下几个方面: 1. **第一个成员的位置** 结构体的第一个成员始终存储在其分配空间的起始地址上[^1]。 2. **其他成员对齐规则** 其他成员会存储在一个称为“对齐数”的整数倍地址上。这个对齐数值取决于两个因素:一是该成员自身的大小;二是当前使用的编译器设置的默认对齐值。最终的实际对齐数是两者之间的较小值[^1]。 3. **不同平台下的差异** 编译器的不同可能导致对齐策略有所区别。例如,在Windows平台上使用Microsoft Visual Studio时,默认对齐数通常为8字节;而在Linux环境下GCC编译器则可能采用4字节作为默认对齐单位[^1]。 4. **整体大小调整** 整个结构体的总尺寸会被扩展到内部所有成员中最长对齐需求长度的一个整数倍。这意味着如果最后一个字段不需要额外填充来达到这一目标,则不会增加多余的空间消耗[^3]。 #### 二、具体实例分析 下面通过几个具体的代码片段进一步说明上述理论如何应用实践当中: ##### 示例1: 单纯字符型与整形组合情况 考虑以下简单定义: ```c #pragma pack(8) // 设置全局对齐参数为8字节 struct Example { char ch; int num; }; ``` 这`ch`占据单个字节而`num`需要四个连续字节存放。由于我们指定了较大的打包指令(`pack=8`),所以即使只有这两个元素存在也至少得预留八位给整个对象[`sizeof(struct Example)`返回值应等于8][^2]. ##### 示例2: 复杂嵌套情形 再来看一段稍微复杂一点的例子涉及到了子结构体的情况: ```c #pragma pack(push, 8) typedef struct _s1{ short a; /* size = 2 */ long b; /* size = 4 or 8 depending on architecture*/ } S1; typedef struct _s2{ char c; /* size = 1 */ S1 d; /* contains 'a' and 'b', total aligned to max of them */ int e; /* size = 4 */ }s2; #pragma pack(pop) ``` 在这个场景,S1类型的变量d本身又包含了多个属性因此它自己的布局也要考虑到各自的数据类型特性再加上外部施加的影响共同决定最后的结果即`s2`的整体规模应该是可以被最高级别也就是long或者double之类的大容量单元所接受的形式呈现出来[^4]. #### 三、总结 综上所述,C编程环境的记录式复合数据形式——结构体会因为多种原因经历一系列复杂的排列过程从而形成特定模式下的物理表现形态这其中包括但不限于初始定位点选取原则、后续各项间间隔距离设定依据还有总体框架外围边界划定标准等等诸多环节相互作用最终达成既定目的同时兼顾性能优化考量. --- 相关问题
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值