【C语言】结构体变量内存分析

首先,我们来看一下下面这段代码。

#include <stdio.h>

int main() 
{
	struct A{
		char a;
		int b;
		double c;
	}part1;
	
	struct B{
		char a;
		double b;
		int c;
	}part_one;
	
	printf("%d %d", sizeof(part1), sizeof(part_one));
	
	return 0;
}

我们不妨假设这两个结构体所占内存字节数为其包含的各个变量所占字节数的代数和,那么,运行结果应该是1 + 4 + 8 和 1 + 8 + 4,也就是13 13。但是,最终我们得到的运行结果却是这样子的:
运行结果

这就不得不提到计算机中存在的一种叫做内存对齐的机制了。

  • 什么是内存对齐呢?
    内存对齐的规则是这样子的(引用自百度百科):

每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的“对齐系数”。

  • 对齐规则:
  1. 数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。
  2. 结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。
  3. 结合1、2可推断:当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。
    注:#pragma pack(n)是用于设定变量以n字节对齐方式对齐(即变量起始位置在内存空间中的偏移量是对齐系数的倍数)。

另外,不得不提到的是,我们的计算机的对齐策略一般是这样子的:
一、起始位置为成员数据类型所占内存的整数倍,若不足则不足部分用数据将内存填充为该数据类型的整数倍。
二、结构体所占总内存为其成员变量中所占空间最大数据类型的整数倍。

本人使用的是x86平台,默认对齐系数为8,因此,综上所述,我们可以对最开始那段代码进行分析。

#include <stdio.h>
#pragma pack(8)

int main() 
{
	struct A{
		char a;	//char型,1 < 8,按1对齐;起始offset为0 
		int b;	//int型,4 < 8,按4对齐;因0被占用,起始offset为4 
		double c;//double型,8 = 8,按8对齐;因0被占用,起始offset为8 
	}part1;
	
	struct B{
		char a;	//char型,1 < 8,按1对齐;起始offset为0
		double b;//double型,8 = 8,按8对齐;因0被占用,起始offset为8
		int c;	//int型,4 < 8,按4对齐;因0,4,8均已被占用,起始offset为16(4是被自动填充,为了保证数据存放) 
	}part_one;
	//对这段代码进行分析之后,我们不难发现其中存在一些"空洞",对于这些空洞,程序会自动补零
	//于是,其最终结果是16和24也就不难理解了
	
	printf("%d %d", sizeof(part1), sizeof(part_one));
	
	return 0;
}

那么,这里又引起了一个新的问题,内存对齐机制是怎么来的呢?
在计算机中通常会让CPU从内存中一次读取若干个字节的数据,而不是一次只读取一个字节的数据,这样的好处是提高了计算机的效率,然而坏处也显而易见。
假设CPU一次从内存中读取四个字节的数据,而现在内存中存在一个char型的数据和一个int型的数据,如果内存不对齐,当CPU第一次跨越四个字节寻址找到了一个char型的数据,而此时CPU的指向到了int型的中间区域,导致这个int型变量未找到,然后CPU会返回去再次寻找,直到找到该int型变量。这样不但没能提高效率,反而增加了CPU的负担。因此我们通常会在第一个char型变量后边填充一部分数据来保证每次寻址时地址都是该数据的整数倍,这样就避免了上述“错误”的发生,也就是所谓的内存对齐。

posted @ 2020-09-09 15:36  Tuzkizki  阅读(226)  评论(0编辑  收藏  举报