C语言中结构体内存分配问题解析。

我们从下面的这个问题开始,问,如果下面的这段代码执行,结果是?
void print_struct_sizeof()
{
	typedef struct A_{
		float a ;  
		char b;
		int c;
		double d;
		int *pa;
		char *pc;
		short e;
	}A;
	cout << "The size of the struct is: " << sizeof(A)<< endl;
}
有这么几个选项:
48, 44, 40等。
在我没有去了解结构体内存如何分配之前,我是这么处理的,我知道每一个变量分配的字节数,然后将他们加起来,分别是:
float a(4) + char b(1) + int c(4) + double d(8) + int *pa(4) + char *pc(1) + short e(2) =  24.
当时看过之后的第一个反应是: My god. 相差这么多。肯定是哪里错了,想想也不可能这么简单的题目,所以便有了下面的学习。
在理解结构体内存分配大小之前需要知道一些基本的知识,包括:

1. 在32bit的机器和64bit的机器中,不同数据类型分配的字节数:
C声明 32bit机器 64bit机器 
char  1 1
short int  2  2
int 4 4
unsigned int  44
long int/long 48
float 4 4
double 8 8 
char * 4 4 (特别需要注意这个) 

如果用sizeof关键字来求这些值的话,一般会得到上面的这些值,但是结果还和编译器有关,所以如果碰到结果不是上面的值也不要奇怪。


2.  下面我们再回到刚才讲的题目中来,如果你在VS中运行,发现上面你的结果是40,但是为什么是40呢?这里先给出一个简单的,直观的解释,其实这种解释并不是本质上的原因,只是为了方便解决问题,其实本质的原因是 编译器处理时的内存对齐机制, 我们后面会进一步谈到。

编译器在为结构体分配内存时,是按照这个结构体中占用内存字节数最大的那个变量的字节数来依次分配的,然后在这块内存中按照结构体变量中的声明顺序来依次分配空间,如果发现当前的空间不足以分配给新的变量,那么会申请新的依旧是这个数量级的内存来进行分配,而之前的会被默认的填充。我们先不解释编译器为什么要这么来做,根据上面的分析,先来看一下刚才那个题目是怎么分配内存的:

double d;是所有这些里面占有内存字节数最大的,是8;所以编译器先分配一个8B的空间。

a占用(4B) + b占用(1B) 还剩3B不够分配给c,所以剩下的3B不做分配,空闲(3B),

再申请8B,c占用(4) + 空闲(4)

再申请8B,d占用(8)

在申请8B, pa(4) + pc(4)

在申请8B, e(2) + 空闲(6)

所以我们可以看到,总共申请了5次, 8B的空间。

为了加深理解,我们将刚才函数中结构体声明变量的顺序换了一下,如下所示:

void print_struct_sizeof()
{
 struct A_{
	int c;  
float b;
char a;
short e;
double d;
int *pa;
char *pc;
}A;
cout << "The size of the struct is: " << sizeof(A)<< endl;
}
此时,输出的是32,大家可以自己计算一下。


3. 下面我们继续深入的学习,编译器为什么要这么做,以及究竟是怎么做的。

我们在其他资料中查到,编译器为了提高CPU的存储速度,对结构体中一些变量的起始地址做了"对齐”处理。在默认情况下,规定各成员变量存放的起始地址相对于结构的起始地址的偏移量必须为该变量的类型所占用的字节数的倍数。如,int型的变量,需要它距离起始地址空间的偏移量为sizeof(int)即4的倍数。而且就是因为这种对齐方式才有了我们上面的分析。而且默认的分配方式要求最后分配的结构体空间的总大小需要是结构体变量中占用最大字节数的整数倍。

下面我们来看一个例子加深理解:

struct MyStruct 
{ 
char dda;//偏移量为0,满足对齐方式,dda占用1个字节; 
double dda1;//下一个可用的地址的偏移量为1,不是sizeof(double)=8的倍数,需要补足7个字节才能使偏移量变为8,因此VC自动填充7个字节,dda1存放在偏移量为8 
int type;//下一个可用的地址的偏移量为16,是sizeof(int)=4的倍 数,满足int的对齐方式,type存 放在偏移量为16的地址上,它占用4个字节。 
};


所有成员变量都分配了空间,空间总的大小为1+7+8+4=20,但总大小不是结构的节边界数(即结构中占用最大空间的类型所占用的字节数sizeof(double)=8)的倍数,所以需要填充4个字节,以满足结构的大小为sizeof(double)=8的倍数。 
当然,编译器也提供给我们自己的方法来进行字节的对齐,通过使用#pragma pack(n)来设定变量以n字节对齐方式。如果以n字节为对齐方式会有出现两种情况:

第一、如果n大于等于该变量所占用的字节数,那么偏移量就使用默认的对齐方式即sizeof()的整数倍。

第二、如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。

结构的总大小也有个约束条件,分下面两种情况:如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数; 否则必须为n的倍数。

下面举例说明其用法。 

#pragma pack(push) //保存对齐状态 

#pragma pack(push) //保存对齐状态 
#pragma pack(4)//设定为4字节对齐 
struct test 
{ 
char m1; 
double m4; 
int m3; 
}; 
#pragma pack(pop)//恢复对齐状态
以上结构的大小为16,下面分析其存储情况,首先为m1分配空间,其偏移量为0,满足我们自己设定的对齐方式(4字节对齐),m1占用1个字节。接着开始为m4分配空间,这时其偏移量为1,需要补足3个字节,这样使偏移量满足为n=4的倍数(因为sizeof(double)大于n),m4占用8个字节。接着为m3分配空间,这时其偏移量为12,满足为4的倍数,m3占用4个字节。这时已经为所有成员变量分配了空间,共分配了16个字节,满足为n的倍数。如果把上面的#pragma pack(4)改为#pragma pack(16),那么我们可以得到结构的大小为24。(请读者自己分析) 

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值