C/C++中的内存补齐机制

本文详细解析了C语言中结构体的内存对齐规则及其影响因素,包括基本数据类型和复合数据类型的对齐方式,并提供了实例代码进行说明。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

      在C语言中,结构是一种复合数据类型,其构成元素既可以是基本数据类型(如int、long、float等)的变量,也可以是一些复合数据类型(如数组、结构、联合等)的数据单元。在结构中,编译器为结构的每个成员按其自然对界(alignment)条件分配空各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构的地址相同。例如,下面的结构各成员空间分配情况:
struct test
{
     char x1;
     short x2;
     float x3;
     char x4;
};
结构的第一个成员x1为char类型,其自然对界为1,其偏移地址为0,占据了第1个字节。第二个成员x2为short类型,其自然对界为2,起始地址必须2字节对界,因此,编译器在x2和x1之间填充了一个空字节。结构的第三个成员x3和第四个成员x4恰好落在其自然对界地址上,在它们前面不需要额外的填充字节。在test结构中,成员x3要求4字节对界,是该结构所有成员中要求的最大对界单元,因而test结构的自然对界条件为4字节,编译器在成员x4后面填充了3个空字节。整个结构所占据空间为12字节。

      更改C编译器的缺省字节对齐方式
在缺省情况下,C编译器为每一个变量或是数据单元按其自然对界条件分配空间。一般地,可以通过下面的方法来改变缺省的对界条件:
     · 使用伪指令#pragma pack (n),C编译器将按照n个字节对齐。
     · 使用伪指令#pragma pack (),取消自定义字节对齐方式。
另外,还有如下的一种方式:
     · __attribute((aligned (n))),让所作用的结构成员对齐在n字节自然边界上。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐。
     · __attribute__ ((packed)),取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。
以上的n = 1, 2, 4, 8, 16... 第一种方式较为常见。
http://blog.youkuaiyun.com/wenddy112/articles/300583.aspx

 

应用实例
  在网络协议编程中,经常会处理不同协议的数据报文。一种方法是通过指针偏移的方法来得到各种信息,但这样做不仅编程复杂,而且一旦协议有变化,程序修改起来也比较麻烦。在了解了编译器对结构空间的分配原则之后,我们完全可以利用这一特性定义自己的协议结构,通过访问结构的成员来获取各种信息。这样做,不仅简化了编程,而且即使协议发生变化,我们也只需修改协议结构的定义即可,其它程序无需修改,省时省力。下面以TCP协议首部为例,说明如何定义协议结构。其协议结构定义如下:

#pragma pack(1) // 按照1字节方式进行对齐
struct TCPHEADER
{
     short SrcPort; // 16位源端口号
     short DstPort; // 16位目的端口号
     int SerialNo; // 32位序列号
     int AckNo; // 32位确认号
     unsigned char HaderLen : 4; // 4位首部长度
     unsigned char Reserved1 : 4; // 保留6位中的4位
     unsigned char Reserved2 : 2; // 保留6位中的2位
     unsigned char URG : 1;
     unsigned char ACK : 1;
     unsigned char PSH : 1;
     unsigned char RST : 1;
     unsigned char SYN : 1;
     unsigned char FIN : 1;
     short WindowSize; // 16位窗口大小
     short TcpChkSum; // 16位TCP检验和
     short UrgentPointer; // 16位紧急指针
};
#pragma pack() // 取消1字节对齐方式

 

下面有一道在 优快云论坛 上讨论火热的题:

#pragma pack(8)
struct s1{
short a;
long b;
};
struct s2{
char c;
s1 d;
long long e;
};
#pragma pack()

1.sizeof(s2) = ?
2.s2的c后面空了几个字节接着是d?


结果如下:
sizeof(S2)结果为24.
成员对齐有一个重要的条件,即每个成员分别对齐.即每个成员按自己的方式对齐.
也就是说上面虽然指定了按8字节对齐,但并不是所有的成员都是以8字节对齐.其对齐的规则是,每个成员按其类型的对齐参数(通常是这个类型的大小)和指定对齐参数(这里是8字节)中较小的一个对齐.并且结构的长度必须为所用过的所有对齐参数的整数倍,不够就补空字节.
S1中,成员a是1字节默认按1字节对齐,指定对齐参数为8,这两个值中取1,a按1字节对齐;成员b是4个字节,默认是按4字节对齐,这时就按4字节对齐,所以sizeof(S1)应该为8;
S2 中,c和S1中的a一样,按1字节对齐,而d 是个结构,它是8个字节,它按什么对齐呢?对于结构来说,它的默认对齐方式就是它的所有成员使用的对齐参数中最大的一个,S1的就是4.所以,成员d就是按4字节对齐.成员e是8个字节,它是默认按8字节对齐,和指定的一样,所以它对到8字节的边界上,这时,已经使用了12个字节了,所以又添加了4个字节的空,从第16个字节开始放置成员e.这时,长度为24,已经可以被8(成员e按8字节对齐)整除.这样,一共使用了24个字节.
                          a    b
S1的内存布局:11**,1111,
                          c    S1.a S1.b     d
S2的内存布局:1***,11**,1111,****11111111
这里有三点很重要:
1.每个成员分别按自己的方式对齐,并能最小化长度
2.复杂类型(如结构)的默认对齐方式是它最长的成员的对齐方式,这样在成员是复杂类型时,可以最小化长度
3.对齐后的长度必须是成员中最大的对齐参数的整数倍,这样在处理数组时可以保证每一项都边界对齐

      补充一下,对于数组,对齐方式为元素的对齐方式。比如:
char a[3];这种,它的对齐方式和分别写3个char是一样的.也就是说它还是按1个字节对齐.
如果写: typedef char Array3[3];
Array3这种类型的对齐方式还是按1个字节对齐,而不是按它的长度.
不论类型是什么,对齐的边界一定是1,2,4,8,16,32,64....中的一个.
struct s1
{
char a[8];
};
struct s2
{
double d;
};
struct s3
{
s1 s;
char a;
};
struct s4
{
s2 s;
char a;
};
cout<<sizeof(s1)<<endl;//8
cout<<sizeof(s2)<<endl;//8
cout<<sizeof(s3)<<endl;//9
cout<<sizeof(s4)<<endl;//16;
s1和s2大小虽然都是8,但是s1的对齐方式是1,s2是8(double),所以在s3和s4中才有这样的差异。
所以,在自己定义结构体的时候,如果空间紧张的话,最好考虑对齐因素来排列结构体里的元素。

      Union的对齐方式:为成员中最大的对齐方式,长度为按照这个对齐方式调整最长成员得到的长度。
union u
{
double a;
int b;
};
union u2
{
char a[13];
int b;
};
union u3
{
char a[13];
char b;
};
cout<<sizeof(u)<<endl;//8
cout<<sizeof(u2)<<endl;//16
cout<<sizeof(u3)<<endl;//13
都知道union的大小取决于它所有的成员中,占用空间最大的一个成员的大小。所以对于u来说,大小就是最大的double类型成员a了,所以 sizeof(u)=sizeof(double)=8。但是对于u2和u3,最大的空间都是char[13]类型的数组,为什么u3的大小是13,而 u2是16呢?关键在于u2中的成员int b。由于int类型成员的存在,使u2的对齐方式变成4,也就是说,u2的大小必须在4的对界上,所以占用的空间变成了16(最接近13的对界)。


      结论:复合数据类型,如union,struct,class的对齐方式为成员中对齐方式最大的成员的对齐方式。


顺便提一下CPU对界问题,32的C++采用8位对界来提高运行速度,所以编译器会尽量把数据放在它的对界上以提高内存命中率。对界是可以更改的,使用#pragmapack(x)宏可以改变编译器的对界方式,默认是8。C++固有类型的对界取编译器对界方式与自身大小中较小的一个。例如,指定编译器按2对界,int类型的大小是4,则int的对界为2和4中较小的2。在默认的对界方式下,因为几乎所有的数据类型都不大于默认的对界方式8(除了 longdouble),所以所有的固有类型的对界方式可以认为就是类型自身的大小。 www. 更改一下上面的程序:
#pragmapack(2)
union u2
{
char a[13];
int b;
};
union u3
{
char a[13];
char b;
};
#pragmapack(8)
cout<<sizeof(u2)<<endl;//14
cout<<sizeof(u3)<<endl;//13
由于手动更改对界方式为2,所以int的对界也变成了2,u2的对界取成员中最大的对界,也是2了,所以此时sizeof(u2)=14。
结论:C++固有类型的对界取编译器对界方式与自身大小中较小的一个。

<think>根据用户的问题,用户想知道如何计算C++类中所有成员变量大小的总和。通过分析引用内容,以下是关键点:1.**计算成员变量大小**:只考虑非静态数据成员,因为静态成员不属于类实例([^4])。2.**虚函数的影响**:如果类包含虚函数,将增加一个指向虚函数表的指针(vfptr),在64位系统上通常占8字节([^5])。3.**继承的影响**:-单继承时,子类会包含父类的非静态数据成员([^2])。-多继承时,可能包含多个虚表指针(vfptr)或多个虚基类指针(vbptr)([^4])。4.**空类大小**:空类的大小为1字节,用于占位([^3])。5.**内存对齐补齐)**:编译器会进行内存对齐,可能增加额外字节([^4])。**计算步骤**:1.**仅统计非静态成员变量**:包括当前类及所有父类的非静态成员变量。-示例:类`e`的成员变量为`intm`,大小为4字节(忽略静态成员`staticintn`)。2.**添加虚函数表指针(若有虚函数)**:-每个包含虚函数的类至少有一个vfptr指针(8字节)。-多继承下可能有多个vfptr([^4])。3.**处理虚基类指针(若有虚继承)**:-虚继承时会产生vbptr指针(8字节)([^4])。4.**内存对齐调整**:实际大小可能因对齐而大于成员变量总和([^4])。**代码示例分析(用户提供的示例)**:```cppclassa{};//空类:大小1字节([^3])classb{};//空类:大小1字节classe{public:voidfunc1();//普通函数不计大小virtualvoidfunc2();//虚函数:增加vfptr(8字节)private:staticintn;//静态成员不计入intm;//int型:4字节(但需考虑对齐)};//类e总大小:vfptr(8)+intm(4)=12,但实际可能因对齐为16字节?需测试验证classc:publica{virtualvoidfun()=0;//纯虚函数:包含vfptr(继承a但a无数据)};//类c:父类a(空类,但作为基类时大小为0?)+vfptr(8)->可能为8字节classd:publicb,publicc{};//多继承:同时继承b和c(均空,但c含vfptr)->可能包含多个指针?大小需计算```**用户代码输出结果分析**(需实际运行,此处推测):-`sizeof(a)`:1(空类)-`sizeof(b)`:1(空类)-`sizeof(c)`:8(vfptr指针)-`sizeof(d)`:8(继承c的vfptr,b为空基类可能优化为0?)-`sizeof(e)`:16(vfptr(8)+intm(4)+补齐4字节?对齐到8的倍数)**回答用户问题**:用户想计算**类中所有成员变量大小的总和**(不包括额外指针和补齐):-仅需累加所有**非静态数据成员**的大小,忽略虚指针、基类子对象等。-示例类`e`中,总和只有`intm`的4字节。**如何用代码实现?**C++没有直接获取成员变量总和的机制,但可手动计算:1.使用`sizeof`获取类总大小。2.减去虚函数指针、虚基类指针、补齐等大小(需明确类结构)。3.**直接累加成员变量大小(推荐)**:遍历类的非静态成员变量并累加`sizeof`。**示例代码**:```cpp#include<iostream>#include<vector>//手动计算类非静态成员大小的函数template<typenameT>structMemberSizeSum{//需要手动特化或使用反射(C++无原生反射)};//手动计算类e的非静态成员大小classe{public:voidfunc1();virtualvoidfunc2();private:staticintn;intm;//假设只有m是普通成员变量};//显式累加成员大小:sizeof(m)=4intmain(){//无法自动计算任意类,但可通过以下方法手动实现:size_tsum=sizeof(e::m);//错误:非静态成员无法直接访问//替代方案:定义变量并计算eobj;intmember_size=sizeof(obj.m);//可行但需知道成员名//总成员变量大小:4std::cout<< "Sumofmembersizesine:"<< member_size<< std::endl;return0;}```**注意事项**:-C++无法直接遍历类的成员变量(需手动累加或使用元编程库如Boost.Fusion)。-实际开发中通常手动计算成员总和。**总结回答**:>C++类中所有成员变量大小的总和指**所有非静态数据成员的sizeof之和**。虚函数表指针、虚基类指针、静态成员等不包含在内。计算时需遍历类的非静态数据成员并累加它们的大小。若类有继承关系,父类的非静态成员也会计入子类大小。注意:内存对齐不影响此总和值(对齐增加的是整体对象大小)。**生成相关问题**:1.如何计算C++类实例占用的实际内存(含虚指针和对齐)?2.C++中虚继承对类大小的影响是什么?3.有没有工具可以自动计算C++类的内存布局?</think> ### C++ 类中所有成员变量大小的总和计算 在 C++ 中,**类中所有成员变量大小的总和**指所有**非静态数据成员**的 sizeof 值之和(不包括任何额外开销),计算时需要遵循以下规则: #### 📌 核心规则(来自引用资料) 1. **只计算非静态数据成员**([^4]) ```cpp class Example { int a; // 计入总和(4字节) static int b; // 静态成员不计入 void func(); // 函数不计入 }; ``` 2. **继承类包含父类成员**([^4][^2]) ```cpp class Base { int x; }; // 大小:4字节 class Derived : Base { int y; }; // 总成员大小:8字节(x + y) ``` 3. **虚函数增加虚表指针**(但此指针**不计入**成员总和)([^4][^5]) ```cpp class WithVirtual { virtual void f(); // 增加虚表指针(额外8字节) int x; // 计入成员总和(4字节) }; // sizeof(WithVirtual) = 12(或16),但成员总和仅4字节 ``` 4. **空类特殊规则**([^3]) ```cpp class Empty {}; // 无成员变量 → 总和为0 ``` #### ⚙️ 计算步骤(针对您的需求) 1. **遍历所有非静态数据成员**(包括继承的) 2. **累加每个成员的 sizeof 值** 3. **忽略以下内容**: - 静态成员([^4]) - 成员函数 - 虚表指针(vfptr)和虚基类指针(vbptr)([^4][^5]) - 编译器填充字节(padding) #### 📐 对您提供代码的分析 ```cpp class e { static int n; // 静态 → 忽略 int m; // 计入 → 4字节 }; // 成员总和 = 4 class c : public a { // 纯虚函数 → 虚表指针不计入成员总和 }; // 成员总和 = 0(无数据成员) ``` #### ✅ 总结公式 **成员变量总和** = $\sum{\text{sizeof(非静态数据成员)}}$ (包括当前类和所有父类的非静态成员) > 💡 **关键区别**: > - **成员变量总和**:仅包含数据成员 > - **类实际大小**:成员总和 + 虚表指针 + 虚基类指针 + padding([^4]) --- ### 📚 相关问题 1. 如何在 C++ 中避免类内存对齐导致的 padding 浪费?[^4] 2. 虚继承对类的大小有什么影响?[^4] 3. 如何用代码自动计算类中所有非静态成员的大小总和? 4. C++17 的 `alignas` 如何影响类成员的内存布局?
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值