LinuxC简谈之结构体的内存对齐和位域的存储

        结构体的成员可以是很多的类型,结构体类型可以定义结构体类型的变量,这样就有各种类型的成员变量。那么,在内存中这些成员变量是如何存储的呢?今天我把我对此的一些理解分享一下。

        首先是结构体的内存对齐。

        结构体的每一个成员变量的首地址要能被自身所占的内存大小所整除,结构体遵循对齐原则,以最长的成员变量类型的长度对齐。不过,每个系统都有一个自己的默认对齐系数,我使用的RED HAT5系统的默认对齐系数是4,而我使用的64位的Ubuntu16.04系统则是8。Windows下默认对齐系数也是,最新出的系统一般是8。如果内存对齐理论与系统的默认对齐系数冲突(对齐宽度超过系统的默认对齐系数),则按系统的默认对齐系数来对齐。

        例如:

struct A
{
	char ch1;
	char ch2;
	int a;
	char ch3;
}a;
printf ("sizeof(a) = %d\n",sizeof(a));         //求结构体类型变量长度


  LinuxC简谈之结构体的内存对齐和位域的存储_第1张图片

           系统的默认对齐系数可以在程序中调整,使用#pragma pack()可以调小默认对齐系数,但不可以调大,但并不建议调整。

           例如: 

#pragma pack(2)           //将默认系数调整为2

struct A
{
	char ch1;
	char ch2;
	int a;
	char ch3;
}a;
printf ("sizeof(a) = %d\n",sizeof(a));         //求结构体类型变量长度


LinuxC简谈之结构体的内存对齐和位域的存储_第2张图片

         为什么要对齐呢?因为以取int型数据,32位系统是以4字节取数据的,如果不对齐,那么可能取一个int型的数据,系统要取两次,这影响了效率。当然,内存对齐提高了运行效率,但也牺牲了一部分空间(有空隙),这就是鱼和熊掌不可兼得。

         接下来分析结构体位域。

         有些数据在存储时并不需要占用一个完整的字节,只需要占用一个或几个二进制位。例如开关只有通电和断电两种状态,用 0 和 1 足以表示,也就是用一个二进位。基于这种考虑,C语言又提供了一种叫做位域的数据结构。在结构体定义时,我们可以指定某个成员变量所占用的二进制位数(bit),这就是位域。

当相邻位域成员的类型相同时,如果它们的位宽之和小于类型大小,那么后面的成员紧邻前一个成员存储;如果它们的位宽之和大于类型大小,那么后面的成员将存在下一个类型大小的空间。

        例如:

struct B
{
	unsigned int a:4;
	unsigned int b:2;
	unsigned int c:12;
}b;
printf ("sizeof(b) = %d\n",sizeof(b));
memset (&b,0,sizeof(b));              //清空内存的值
b.a = 1;
b.b = 1;
b.c = 1;
int *p = (int *)&b;               //使用指针指向结构体变量首地址,类型为int
printf ("b = %d\n",*p);        //以整型格式输出结构体变量代表的内存中的值


LinuxC简谈之结构体的内存对齐和位域的存储_第3张图片

        当相邻位域成员的类型不同时,不同的编译器有不同的实现方案,GCC 会压缩存储,而 VC/VS 不会。我实际试验过好多次,我发现,在GCC下,其实和上一种情况一样。不管每个位域成员变量占几位,一个位域成员变量都只能存在一个它自己类型大小的空间内,比如char型的只能存在一个字节内,不可以跨字节存储;同理int型只能存储在4个字节内。只要存的下,它们可以紧挨在一起。

        例如:

struct C
{
	unsigned int a:4;
	unsigned char b:2;
	unsigned int c:12;
}c;
printf ("sizeof(c) = %d\n",sizeof(c));
memset (&c,0,sizeof(c));
c.a = 1;
c.b = 1;
c.c = 1;
int *p = (int *)&c;
printf ("c = %d\n",*p);


LinuxC简谈之结构体的内存对齐和位域的存储_第4张图片

        一旦这一个类型大小的空间存不下,就只能存在下一个类型大小的空间。

        例如:

struct D
{
	unsigned int a:4;
	unsigned char b:5;
	unsigned int c:12;
}d;
printf ("sizeof(d) = %d\n",sizeof(d));
memset (&d,0,sizeof(d));
d.a = 1;
d.b = 1;
d.c = 1;
int *p = (int *)&d;
printf ("d = %d\n",*p);


LinuxC简谈之结构体的内存对齐和位域的存储_第5张图片

         这也符合对齐理论。系统以一个字节大小为单位读取char型数据,不可能对取一个数据要取两次。

         如果成员之间穿插着非位域成员,会视情况进行压缩。即对位域成员变量压缩,而不压缩非位域成员变量。

         例如:

struct E
{
	unsigned int a:4;
	unsigned char b;
	unsigned int c:12;
}e;
printf ("sizeof(e) = %d\n",sizeof(e));
memset (&e,0,sizeof(e));
e.a = 1;
e.b = 1;
e.c = 1;
int *p = (int *)&e;
printf ("e = %d\n",*p);


LinuxC简谈之结构体的内存对齐和位域的存储_第6张图片

         位域成员可以没有名称,只给出数据类型和位宽,因为没有名称,无名位域不能使用。一般用来作填充或者调整成员位置。

         例如: 

struct F
{
	unsigned int a:4;
	unsigned int :10;     //无名位域
	unsigned int c:12;
};


          涉及到内存和二进制位的知识其实很有趣,大家有兴趣不妨可以探索一下。

你可能感兴趣的:(LinuxC简谈)