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

先来看一段代码,这是曾经的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#






### HighTec 编译器结构体内存对齐规则及检测方法 HighTec 编译器基于 GCC,因此其结构体内存对齐规则 GCC 的规则类似。以下是关于 HighTec 编译器中结构体的内存对齐规则及其检测方法的详细说明。 #### 1. 内存对齐规则 在 HighTec 编译器中,结构体成员的默认对齐方式取决于目标平台的架构和编译器设置。通常情况下,编译器会根据每个成员的自然对齐要求(即数据类型的大小)进行对齐[^1]。例如: - 如果结构体包含一个 `uint32_t` 类型的成员,则该成员会被对齐到 4 字节边界。 - 如果结构体包含一个 `uint64_t` 类型的成员,则该成员会被对齐到 8 字节边界。 如果需要自定义对齐方式,可以使用 `__attribute__((aligned(n)))` 或者 `#pragma pack` 指令来调整对齐规则。例如: ```c // 使用 __attribute__((aligned(n))) 设置对齐 struct Example { uint8_t a; uint32_t b; } __attribute__((aligned(8))); // 使用 #pragma pack 设置对齐 #pragma pack(1) struct Example { uint8_t a; uint32_t b; }; #pragma pack() ``` 需要注意的是,当使用 `__attribute__((aligned(n)))` 时,如果某个成员的自然对齐需求大于指定的对齐值 `n`,则仍以较大的对齐值为准[^1]。 #### 2. 检测方法 为了验证结构体的内存布局和对齐方式,可以通过以下方法进行检测: ##### 方法一:使用 `offsetof` 和 `sizeof` `offsetof` 宏可以用来计算结构体成员相对于结构体起始位置的偏移量。结合 `sizeof`,可以验证结构体的整体大小和成员对齐情况。例如: ```c #include <stdio.h> #include <stddef.h> struct Example { uint8_t a; uint32_t b; }; int main() { printf("Offset of 'a': %zu\n", offsetof(struct Example, a)); printf("Offset of 'b': %zu\n", offsetof(struct Example, b)); printf("Size of struct: %zu\n", sizeof(struct Example)); return 0; } ``` ##### 方法二:通过调试工具检查内存布局 在嵌入式开发中,可以使用调试工具(如 JTAG 调试器或 IDE 提供的调试功能)查看结构体实例的内存分布。通过观察每个成员的实际地址,可以判断是否符合预期的对齐规则。 ##### 方法三:编译器特定选项 HighTec 编译器支持输出结构体布局的调试信息。可以通过启用编译器选项(如 `-fdump-lang-all` 或类似的选项)生成详细的结构体布局报告。具体选项需要参考 HighTec 编译器的手册[^3]。 #### 3. 注意事项 - 在 AUTOSAR 环境下开发时,应确保协议双方(如 ECU 之间的通信)的字节对齐方式一致[^4]。 - 如果需要取消字节对齐,可以使用 `#pragma pack(1)`,但需注意可能带来的性能下降和访问效率降低的问题。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值