C++ 结构体字节对齐问题

转载来自:http://www.cnblogs.com/longlybits/articles/2385343.html

                                                       结构体字节对齐

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

      内存对齐的原因:

      1)某些平台只能在特定的地址处访问特定类型的数据;

      2)提高存取数据的速度。比如有的平台每次都是从偶地址处读取数据,对于一个int型的变量,若从偶地址单元处存放,则只需一个读取周期即可读取该变量;但是若从奇地址单元处存放,则需要2个读取周期读取该变量。

       win32平台下的微软C编译器对齐策略:

      1)结构体变量的首地址能够被其最宽数据类型成员的大小整除。编译器在为结构体变量开辟空间时,首先找到结构体中最宽的数据类型,然后寻找内存地址能被该数据类型大小整除的位置,这个位置作为结构体变量的首地址。而将最宽数据类型的大小作为对齐标准。

      2)结构体每个成员相对结构体首地址的偏移量(offset)都是每个成员本身大小的整数倍,如有需要会在成员之间填充字节。编译器在为结构体成员开辟空 间时,首先检查预开辟空间的地址相对于结构体首地址的偏移量是否为该成员大小的整数倍,若是,则存放该成员;若不是,则填充若干字节,以达到整数倍的要 求。

      3)结构体变量所占空间的大小必定是最宽数据类型大小的整数倍。如有需要会在最后一个成员末尾填充若干字节使得所占空间大小是最宽数据类型大小的整数倍。

 

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

1.test1   空结构体

typedef struct  node
{
     
}S;

则sizeof(S)=1;或sizeof(S)=0;

在C++中占1字节,而在C中占0字节。

2.test2

typedef struct  node1
{
     int  a;
     char  b;
     short  c;
}S1;

则sizeof(S1)=8。这是因为结构体node1中最长的数据类型是int,占4个字节,因此以4字节对齐,则该结构体在内存中存放方式为

|--------int--------|   4字节

|char|----|--short-|   4字节

总共占8字节

3.test3

typedef struct  node2
{
     char  a;
     int  b;
     short  c;
}S2;

 则siezof(S3)=12.最长数据类型为int,占4个字节。因此以4字节对齐,其在内存空间存放方式如下:

|char|----|----|----|  4字节

|--------int--------|  4字节

|--short--|----|----|  4字节

总共占12个字节

4.test4  含有静态数据成员 

typedef struct  node3
{
     int  a;
     short  b;
     static  int  c;
}S3;

则sizeof(S3)=8.这里结构体中包含静态数据成员,而静态数据成员的存放位置与结构体实例的存储地址无关(注意只有在C++中结构体中才能含有静态数据成员,而C中结构体中是不允许含有静态数据成员的)。其在内存中存储方式如下:

|--------int--------|   4字节

|--short-|----|----|    4字节

而变量c是单独存放在静态数据区的,因此用siezof计算其大小时没有将c所占的空间计算进来。

5.test5  结构体中含有结构体

typedef struct  node4
{
     bool  a;
     S1 s1;
     short  b;
}S4;

则sizeof(S4)=16。是因为s1占8字节,而s1中最长数据类型为int,占4个字节,bool类型1个字节,short占2字节,因此以4字节对齐,则存储方式为

|-------bool--------|  4字节

|-------s1----------|  8字节

|-------short-------|  4字节

6.test6

typedef struct  node5
{
     bool  a;
     S1 s1;
     double  b;
     int  c;
}S5;

则sizeof(S5)=32。是因为s1占8字节,而s1中最长数据类型为int,占4字节,而double占8字节,因此以8字节对齐,则存放方式为:

|--------bool--------|    8字节

|---------s1---------|    8字节

|--------double------|    8字节

|----int----|---------|     8字节 

7.test7 

若在程序中使用了#pragma pack(n)命令强制以n字节对齐时,默认情况下n为8.

则比较n和结构体中最长数据类型所占的字节大小,取两者中小的一个作为对齐标准。

若需取消强制对齐方式,则可用命令#pragma pack()

如果在程序开头使用命令#pragma pack(4),对于下面的结构体

typedef struct  node5
{
     bool  a;
     S1 s1;
     double  b;
     int  c;
}S5;

则sizeof(S5)=24.因为强制以4字节对齐,而S5中最长数据类型为double,占8字节,因此以4字节对齐。在内存中存放方式为:

  |-----------a--------|   4字节

  |--------s1----------|   4字节

  |--------s1----------|   4字节

  |--------b-----------|   4字节

  |--------b-----------|   4字节

  |---------c----------|    4字节

总结一下,在计算sizeof时主要注意一下几点:

1)若为空结构体,则只占1个字节的单元

2)若结构体中所有数据类型都相同,则其所占空间为 成员数据类型长度×成员个数

若结构体中数据类型不同,则取最长数据类型成员所占的空间为对齐标准,数据成员包含另一个结构体变量t的话,则取t中最 长数据类型与其他数据成员比较,取最长的作为对齐标准,但是t存放时看做一个单位存放,只需看其他成员即可。

3)若使用了#pragma pack(n)命令强制对齐标准,则取n和结构体中最长数据类型占的字节数两者之中的小者作为对齐标准。

 

另外除了结构体中存在对齐之外,普通的变量存储也存在字节对齐的情况,即自身对齐。编译器规定:普通变量的存储首地址必须能被该变量的数据类型宽度整除。

测试程序:

?
/*测试sizeof运算符  2011.10.1*/
 
#include <iostream>
using  namespace  std;
//#pragma pack(4)    //设置4字节对齐
//#pragma pack()     //取消4字节对齐
 
typedef  struct  node
{
     
}S;
 
typedef  struct  node1
{
     int  a;
     char  b;
     short  c;
}S1;
 
typedef  struct  node2
{
     char  a;
     int  b;
     short  c;
}S2;
 
typedef  struct  node3
{
     int  a;
     short  b;
     static  int  c;
}S3;
 
typedef  struct  node4
{
     bool  a;
     S1 s1;
     short  b;
}S4;
 
typedef  struct  node5
{
     bool  a;
     S1 s1;
     double  b;
     int  c;
}S5;
 
 
 
int  main( int  argc,  char  *argv[])
{
     cout<< sizeof ( char )<< " " << sizeof ( short )<< " " << sizeof ( int )<< " " << sizeof ( float )<< " " << sizeof ( double )<<endl;
     S s;
     S1 s1;
     S2 s2;
     S3 s3;
     S4 s4;
     S5 s5;
     cout<< sizeof (S3)<<endl;
     cout<< sizeof (s)<< " " << sizeof (s1)<< " " << sizeof (s2)<< " " << sizeof (s3)<< " " << sizeof (s4)<< " " << sizeof (s5)<<endl;
     return  0;
}
<think>首先,用户的问题是:“我想理解或解决C++结构体在内存中的字节对齐问题 请问C++ 结构体 字节对齐 内存布局”。这是一个关于C++结构体字节对齐和内存布局的问题。 根据系统级指令: - 所有行内数学表达式必须使用$...$格式,例如:$x^2$。 - 独立公式必须使用$$...$$格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识,例如:[^1]。 用户可见层指令: - 回答结构清晰,帮助用户逐步解决问题,尽量保证回答真实可靠。 参考站内引用:用户提供了几个引用,我需要参考这些,但用户说“上述引用仅供参考,并不是用户真正的需求,请尽可能多的参考用户上一次的问题和你的回答”。用户没有指定上一次问题,所以我应该基于当前问题回答。 关键内容: - 引用[1]到[5]讨论了C++结构体字节对齐的规则和示例。 - 规则包括:字段对齐、整个结构体大小调整、最大对齐参数等。 核心字节对齐规则: 1. 每个成员变量的起始地址必须是其类型大小的倍数。 2. 结构体的总大小必须是其最大成员大小的倍数。 3. 编译器可能会在成员之间插入填充字节以满足对齐要求。 我需要构建一个清晰、逐步的解释: 1. 介绍字节对齐的概念。 2. 解释对齐规则。 3. 提供示例。 4. 讨论为什么需要字节对齐(性能原因)。 5. 如何控制对齐(如使用#pragma pack)。 使用中文回答。 生成相关问题:在回答结束后,添加一个“相关问题”部分。 结构回答: - 开头:简要介绍问题。 - 主体:逐步解释规则、示例、内存布局。 - 结尾:总结和生成相关问题。 确保使用LaTeX格式: - 行内数学:如成员大小$sizeof(type)$。 - 独立公式:如果需要,例如计算大小。 示例: - 使用引用中的例子,如struct ISS或struct CC。 引用标识:在回答中引用参考时添加[^n],但用户提供的引用是给我的参考,不是直接引用内容。我应该基于知识回答,但引用这些参考。 用户说:“上述引用仅供参考”,所以我应该使用这些信息来构建回答,但不必直接复制。 最终回答结构: 1. 什么是字节对齐?为什么重要? 2. C++结构体字节对齐规则。 3. 示例分析。 4. 如何查看和修改对齐。 5. 总结。 6. 相关问题。 生成相关问题: - 类似示例中的,如“如何计算结构体大小?”、“如何避免不必要的填充?”等。</think>### C++结构体字节对齐与内存布局详解 在C++中,结构体(struct)的内存布局涉及字节对齐(byte alignment),这是编译器为优化内存访问性能而采用的规则。如果未对齐,CPU访问未对齐数据可能引发性能下降或硬件异常(如某些架构)。我将逐步解释核心规则、内存布局原理,并提供实用示例。所有分析基于C++标准(C++11及以上)和常见编译器行为(如GCC、MSVC)。 #### 1. **字节对齐的基本概念** 字节对齐要求每个结构体成员的起始地址必须是其类型大小的整数倍: - 例如,`int`类型通常大小为4字节(32位系统),因此其起始地址必须是4的倍数(如0x0000、0x0004)。 - 对齐规则由**最大对齐参数**决定:结构体总大小必须是所有成员中最大对齐值的整数倍。 - 为什么需要对齐?CPU访问对齐数据更高效,减少内存总线周期;未对齐数据可能导致分段错误或性能损失[^1][^3]。 数学上,成员起始地址需满足: $$\text{address} \% \text{alignof(type)} = 0$$ 其中$\text{alignof(type)}$是类型的对齐值(通常等于$sizeof(\text{type})$)。 #### 2. **核心对齐规则** C++结构体字节对齐遵循三条规则[^2][^3][^5]: 1. **成员对齐规则**:每个成员起始地址必须为其类型大小的倍数。 - 例如,`short`(2字节)起始地址需是2的倍数。 2. **填充规则**:编译器在成员间插入填充字节(padding)以满足对齐要求。 - 填充内容未定义(通常为0)。 3. **整体对齐规则**:结构体总大小必须是最大成员对齐值的整数倍。 - 最大对齐值:取所有成员类型对齐值的最大值。 #### 3. **内存布局分析示例** 我们以用户引用中的`struct ISS`为例(引用[1]),逐步分析内存布局: ```cpp struct OLS { long a; // 假设long为8字节(64位系统) short b; // 2字节 }; // sizeof(OLS) = 16? 规则计算后解释 struct ISS { short ma; // 2字节 int mb; // 4字节 OLS mc; // 嵌套结构体 short md; // 2字节 }; ``` **步骤1: 计算成员对齐和填充** - `ma`(short, 2字节):起始地址0x0000(假设基址0),大小2字节。 - `mb`(int, 4字节):起始地址需为4的倍数。当前地址0x0002不满足,插入2字节填充 → 地址0x0004开始,大小4字节。 - `mc`(OLS, 嵌套结构体): - 先计算`OLS`:`long a`(8字节)需8字节对齐,起始地址0x0008;`short b`(2字节)起始地址0x0010。但`OLS`整体大小需为最大对齐值(8字节)的倍数:当前大小10字节(8+2),需补6字节填充 → `sizeof(OLS) = 16`字节。 - `mc`在`ISS`中起始地址:`mb`结束于0x0007,下一地址0x0008满足8字节对齐 → 直接放置。 - `md`(short, 2字节):起始地址0x0018(`mc`结束于0x0017),大小2字节。 **步骤2: 计算整体大小并填充** - 当前总大小:`ma`(2) + 填充(2) + `mb`(4) + `mc`(16) + `md`(2) = 26字节。 - 最大对齐值:取`ma`(2)、`mb`(4)、`mc`(8)、`md`(2)的最大值 → 8字节。 - 26不是8的倍数,需在末尾补6字节填充 → `sizeof(ISS) = 32`字节。 内存布局可视化(单位:字节): | 地址范围 | 内容 | 说明 | |----------|------------|---------------| | 0x0000-0x0001 | `ma` | short数据 | | 0x0002-0x0003 | 填充 | 满足`mb`对齐 | | 0x0004-0x0007 | `mb` | int数据 | | 0x0008-0x000F | `mc.a` | long数据 (OLS部分) | | 0x0010-0x0011 | `mc.b` | short数据 (OLS部分) | | 0x0012-0x0017 | 填充 | OLS内部填充 | | 0x0018-0x0019 | `md` | short数据 | | 0x001A-0x001F | 填充 | 整体对齐填充 | 验证:引用[1]中调试输出`sizeof(ISS) = 32`,符合计算。 #### 4. **影响对齐的因素与控制方法** - **编译器默认行为**:编译器根据目标平台自动对齐(如x86-64通常8字节对齐)。 - **修改对齐**: - 使用`#pragma pack(n)`:强制按n字节对齐(n=1,2,4...)。 ```cpp #pragma pack(1) // 1字节对齐,无填充 struct TightPacked { int a; // 4字节 char b; // 1字节 double c; // 8字节 }; // sizeof = 13 (无填充) #pragma pack() // 恢复默认 ``` - C++11的`alignas`:指定成员对齐。 ```cpp struct CustomAlign { alignas(16) int a; // 强制16字节对齐 char b; }; ``` - **性能权衡**:紧密对齐(如`#pragma pack(1)`)节省内存,但可能降低访问速度[^3][^5]。 #### 5. **常见问题与优化建议** - **为什么结构体大小不等于成员之和?** 填充字节导致,如引用[3]中`struct CC`大小32而非25。 - **优化内存**: - 重排成员:按对齐值降序排列(如double→int→short→char),减少填充。 ```cpp // 优化前:sizeof ≈ 12 (含填充) struct BadOrder { char a; double b; char c; }; // 优化后:sizeof = 16 (更紧凑) struct GoodOrder { double b; int a; char c; }; ``` - 使用位域(bit-field):对bool或小范围整数压缩。 ```cpp struct BitField { unsigned a : 4; // 4位 unsigned b : 4; }; // sizeof可能为1字节 ``` - **调试工具**:使用`offsetof`宏查看成员偏移,或编译器选项输出内存布局。 #### 6. **总结** 字节对齐C++结构体内存布局的核心机制,由成员对齐、填充和整体对齐规则构成。默认行为平衡性能与内存,但可通过`#pragma pack`或成员重排优化。理解这些规则有助于避免内存浪费和访问错误。实际开发中,使用`sizeof`和`offsetof`验证布局[^2][^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值