结构体成员内存分布与对齐

先来看一段代码,这是曾经的IBM和微软的笔试题:

#include <iostream>

struct student
{
    char name[10];
    int age;
    double score;
};

int main (void)
{
    using namespace std;
    struct student st;
    cout << sizeof(st) << endl;
    cout << (unsigned int)&st.name << endl;
    cout << (unsigned int)&st.age << endl;
    cout << (unsigned int)&st.score << endl;
}

输出的结果是:


为什么结果会是这样的呢?按理来说,st结构体的大小应该是10+4+8=22才对,怎么会是24呢?

其实原因在于,结构体在存放到内存中时是要进行内存对齐的,那么究竟是怎么对齐的呢?下面来探讨一下:

要弄清楚上面问题是怎么回事,就要搞清楚结构体在内存中是怎么存放的。其实这些问题即与硬件相关,又与软件相关。所谓的与软件相关是说和具体的语言和编译器有关,编译器为了优化CPU访问内存的效率,在生成结构体成员的起始地址时遵循某种特定的规则,这就是结构体成员对齐;所谓硬件相关是说和CPU的字节序有关,也就是大于一个字节型的数据如int,long等在内存中存放的顺序问题,即单个字节中bit位和地址的高低对应的关系。字节序分为两类:Big-Endian和Little-Endian,通常称为大端法和小端法,他们的定义是:

大端法:将一个字节中的每个bit位中的低位放在地址中的高位处,而每个bit位中高位放在地址中的低位

小端法:将一个字节中的每个bit位中的低位放在地址中的低位处,而每个bit位中高位放在地址中的高位

Intel,VAX,Unisys处理器的计算机都是小端法,而IBM大型机和UNIX平台的机器都是大端法的。这里我们仅讨论Intel小端法的机器。

前面说了,为了优化CPU对内的访问效率,编译器做了内存分配的处理,处理的规则大致如下:

对与n字节的元素,他的首地址能被n整除,这种规则称为“对齐”。对于结构体来说结构体成员在内存中按顺序存放,所占字节地址依次升高,第一个成员处于低地址,最后一个成员处于最高的地址,但是成员的存储不一定是连续的,编译器会对每个成员做前面介绍的“对齐”当时处理。

通常编译器中可以设置一个对齐参数n,但是这个n并不是成员的实际对齐参数,如在大多数的IDE中(如VC 6.0)中的实际对齐参数N是这样计算得到的:N=min(sizeof(成员类型),n),也就是说N取成员类型的字节数和编译器的对其参数中的最小值,结构体所有成员的N最大值称为结构体的对其参数。

接下来,我们详细讨论结构体成员的分步情况。顺便做个假设,常见类型的大小:int 4字节,short 2字节,char 1字节,double 8字节,long 4字节

还分析开头的那个例子:

#include <iostream>

struct student
{
    char name[10];
    int age;
    double score;
};

int main (void)
{
    using namespace std;
    struct student st;
    cout << sizeof(st) << endl;
    cout << (unsigned int)&st.name << endl;
    cout << (unsigned int)&st.age << endl;
    cout << (unsigned int)&st.score << endl;
}

在这里n值是8(一般IDE默认),各个成员在内存中的分布情况如下(横线表示该内存单元被占据):

————————————————
————  ————————
————————————————
name数组肯定要分配在一个连续的内存空间中,但是首地址必须是8的倍数,所以这里首地址是2686728,当他分配完毕时,下面该分配age的内存了,这个时候name尾地址是2686737,下一个是2686738,他不能被N=min(4,8)整除,直到2686740才整除,所以这个是age的首地址,后面的score同理,故如上如图显示的那样,本程序中的st结构体共占用24个字节空间,两个被浪费。

如果我们改变IDE中的n值呢?可以用#pragma pack(n)  //n=1,2,4,8,16来改变n值,其结果的分析和上面一样

实例代码:

/*************************************************************************
	> File Name: nei_cun_dui_qi.c
	> Author: Baniel Gao 
	> Mail: createchance@163.com 
	> Blog: blog.youkuaiyun.com/createchance 
	> Created Time: Mon 16 Dec 2013 19:19:03 CST
 ************************************************************************/
#include <stdio.h>
#pragma pack(2)

typedef struct
{
	char name[10];
	int num;
	double price;
} BOOK;

int main(void)
{
	BOOK book;

	printf("&book.name = %p \n",book.name);
	printf("&book.num = %p \n",&book.num);
	printf("&book.price = %p \n",&book.price);
	printf("sizeof(BOOK) = %ld \n",sizeof(BOOK));
	
	return 0;
}

运行结果:

root@ubuntu:/home/linux/Code/Section1# ./a.out
&book.name = 0x7fffa6a5f460
&book.num = 0x7fffa6a5f46a
&book.price = 0x7fffa6a5f46e
sizeof(BOOK) = 22
root@ubuntu:/home/linux/Code/Section1#






### C/C++ 结构体嵌套时的内存布局及对齐方式 在 C/C++ 中,当结构体嵌套时,其内存布局不仅受到单个成员变量的影响,还涉及外部和内部结构体之间的对齐规则。以下是关于嵌套结构体内存布局的关键点: #### 1. 嵌套结构体对齐规则 对于嵌套结构体,外层结构体会将其视为一个整体对象来处理。因此,在计算外层结构体的 `sizeof` 时,会考虑内层结构体的整体大小及其对齐需求。 - **内层结构体对齐** 内部结构体自身的对齐遵循其最大基本数据类型的对齐要求[^2]。如果该结构体包含其他子结构体,则这些子结构体也会按照上述原则进行对齐。 - **外层结构体对齐** 外层结构体的最终对齐值取决于两个因素:一是它所含的最大基本类型或子结构体对齐值;二是整个结构体本身的大小需要满足其最大对齐单位的整数倍[^3]。 #### 2. 计算嵌套结构体的大小 假设有一个简单的嵌套结构体如下所示: ```c struct Inner { char c; int i; }; struct Outer { short s; struct Inner inner; }; ``` - 首先计算 `Inner` 的大小和对齐值: - 成员 `char c` 占用 1 字节,对齐到 1 字节边界。 - 成员 `int i` 占用 4 字节,对齐到 4 字节边界。 - 总大小为 `(max(1, 4)) * ceil((1 + padding + 4) / max)` = 8 字节(其中填充字节数使总大小成为 4 的倍数)[^1]。 - 接着计算 `Outer` 的大小和对齐值: - 成员 `short s` 占用 2 字节,对齐到 2 字节边界。 - 子结构体 `inner` 被视作单一单元,占用 8 字节并按 4 字节对齐。 - 整体大小需调整至最大对齐值(即 4 或更高)的倍数,因此可能增加额外填充。 最终结果可能是这样的: - `sizeof(struct Inner)` = 8 字节; - `sizeof(struct Outer)` = 16 字节(具体依赖编译器设置)。 #### 3. 影响内存布局的因素 影响嵌套结构体内存布局的主要因素包括但不限于以下几点: - 编译器默认的对齐选项(可通过 `_Alignas` 或 `#pragma pack(n)` 修改)。 - 各种平台架构下不同数据类型的自然对齐值差异。 - 是否存在位域字段或其他特殊定义形式。 --- ### 示例代码展示 下面通过一段实际代码演示如何查看嵌套结构体的具体内存分布情况: ```c #include <stdio.h> #include <stddef.h> #pragma pack(push, 1) typedef struct { char a; // Offset: 0 (size=1) } S1; typedef struct { double b; // Offset: 0 (size=8), align to 8 bytes boundary. } S2; typedef struct { S1 s1; // Offset: 0 (size=1). S2 s2; // Offset: 8 (size=8). Aligned at next multiple of its own alignment value (which is 8 here). } NestedStruct; #pragma pack(pop) int main() { printf("Sizeof(S1): %zu\n", sizeof(S1)); // Output depends on packing rules applied above. printf("Offset(s1.a): %td\n", offsetof(NestedStruct, s1.a)); printf("Offset(s2.b): %td\n", offsetof(NestedStruct, s2.b)); return 0; } ``` 运行此程序可以观察到各部分偏移量的变化规律,并验证理论分析是否一致。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值