前段时间做了一个生成bin数据的界面工具。大概的工作流程是先把数据填充到结构体内,然后按照结构体的数据结构,把数据用二进制方式写成bin文件。在生成bin的过程中,遇到一个比较有意思的问题,在这里和大家分享一下。
当在C中定义了一个结构类型时,它的大小是否等于各字段(field)大小之和?在回答这个问题前,希望各位不清楚的朋友先编写2个比较简单的测试程序,先试一试,有个直观的体会。
测试程序1:
#include <stdio.h>
typedef struct{
char x;
char y;
}TESTSTRUCT;
int main(void)
{
TESTSTRUCT a;
printf("%d/n",sizeof(a));
}
测试程序2:
#include <stdio.h>
typedef struct{
char x;
int y;
}TESTSTRUCT;
int main(void)
{
TESTSTRUCT a;
printf("%d/n",sizeof(a));
}
上面2个简单的代码输出分别是2和8。在分析这2个答案的时候,先了解另外的一个概念,内存对齐。许多实际的计算机系统对基本类型数据在内存中存放的位置有限制,它们会要求这些数据的首地址的值是某个数k(通常它为4或8)的倍数,这就是所谓的内存对齐,而这个k则被称为该数据类型的对齐模数(alignment modulus)。当一种类型S的对齐模数与另一种类型T的对齐模数的比值是大于1的整数,我们就称类型S的对齐要求比T强(严格),而称T比S弱(宽松)。这种强制的要求一来简化了处理器与内存之间传输系统的设计,二来可以提升读取数据的速度。比如这么一种处理器,它每次读写内存的时候都从某个8倍数的地址开始,一次读出或写入8个字节的数据,假如软件能保证double类型的数据都从8倍数地址开始,那么读或写一个double类型数据就只需要一次内存操作。否则,我们就可能需要两次内存操作才能完成这个动作,因为数据或许恰好横跨在两个符合对齐要求的8字节内存块上。
现在回到我们关心的struct上来。ANSI C规定一种结构类型的大小是它所有字段的大小以及字段之间或字段尾部的填充区大小之和。这里的填充区域就是指为了使结构体字段满足内存对齐要求而额外分配给结构体的空间。让我们来分析上面2个代码:
第一个结构体是这样定义的:
typedef struct{
char x;
char y;
}TESTSTRUCT;
那么它在内存中的分配应该是
__________________
| | |
| x | y |
| | |
+---------------+
Bytes: 1 1
我们假设内存地址从左至右递增,由于x y都是一个字节,在内存中存储都是按照1个字节来的,那么这个结构体就不许要进行内存对齐,也就不存在内存填充区,这样输出的结构体大小应该为2
第二个结构体的定义为:
typedef struct{
char x;
int y;
}TESTSTRUCT;
由于x y字段在内存中分配的内存分别为1 4。则结构体在内存中并不能满足内存对齐的要求,那么编译器就会进行优化,满足内存对齐的要求,会产生如下的情况:
_______________________________________
| |///| |
| x |//padding| y |
| |///| |
+-------------------------------------+
Bytes: 1 3 4
这样就导致在测试程序2中输出结构体的大小为8。
不同的编译器会有不同的优化方式。我通过在windowsXP VC6.0和linux redhat9 GCC 2种编译器分别对下面的代码进行测试
#include <stdio.h>
typedef struct{
char x;
double y;
}TESTSTRUCT;
int main(void)
{
TESTSTRUCT a;
printf("%d/n",sizeof(a));
}
在VC6.0中,该结构体的大小为16,而在GCC编译的下面,该结构体的大小为12。我对这2种编译器的内存对齐优化的猜想如下。
1.在VC6.0中,编译器对结构体的内存对齐优化是基于最大字段进行优化。
__________________________________
| |///| |
|x |//padding/| y |
| |///| |
+--------------------------------+
Bytes: 1 7 8
由于y是最大字段8,而x的大小为1,需要按照8个字节进行内存优化,则要填充7个字节,以满足内存对齐的要求。
2.在GCC中,编译器是基于4的倍数进行优化
_______________________________________
| |///| |
|x |//padding//| y
| |///| |
+-------------------------------------+
Bytes: 1 3 8
y是最大字段8,而x为1,则进行4倍数进行内存优化,整个结构体的大小就为12。为进一步理解对GCC编译器的内存对齐优化。进行如下测试:
1.
#include <stdio.h>
typedef struct{
int x;
double y;
}TESTSTRUCT;
int main(void)
{
TESTSTRUCT a;
printf("%d/n",sizeof(a));
}
2.
#include <stdio.h>
typedef struct{
int x;
char z;
double y;
}TESTSTRUCT;
int main(void)
{
TESTSTRUCT a;
printf("%d/n",sizeof(a));
}
这2次输出分别为12和16,与预期的猜想一样。
通过以上分析,可以大概了解结构体内存对齐的方式,以及不同编译器对结构体内存对齐优化方式的不同。在VC6.0中,可以对内存对齐的方式进行修改,只需要在预处理的时候使用pack函数,对编译器的结构体内存对齐进行重新设置就可以。如:
#pragma pack(1)
则编译器就会按照1个字节的方式进行内存对齐,至于GCC的内存对齐方式还不太清楚。有哪位了解的朋友可以给我留言进行科普一下,万分感谢。