结构是一个或多个变量的集合,这些变量可能为不同的类型,为了处理的方便而将这些变量组织在一个名字之下;

下面先来看一个定义结构体的例子:

#include <stdio.h>
#include <stdlib.h>

struct testType{
    char first;
    int second;
    double third;
};

int main()
{
    struct testType mytest;
    printf("mytest地址是:%x\n", &mytest);
    printf("char变量地址是:%x\n", &mytest.first);
    printf("int变量地址是:%x\n", &mytest.second);
    printf("double变量地址是:%x\n", &mytest.third);
    printf("%d\n", sizeof(mytest));
    system("pause");
    return 0;
}

在该结构体中定义了一个char类型的变量first,一个int类型的变量second和一个double类型的变量third;在VS2013下进行编译运行得到如下结果

结构体内存对齐_结构体 内存对齐 数据类型 内存大小

  按照我的理解char变量占一个字节,那么int的地址就是mytest+1。但从运行结果来看,int的地址却是成了mytest+4。此时我就想到“内存对齐",内存对齐指的是:

  对于大部分程序员来说,“内存对齐”对他们来说都应该是“透明的”。“内存对齐”应该是编译器的“管辖范围”。编 译器为程序中的每个“数据单元”安排在适当的位置上。但是C语言的一个特点就是太灵活,太强大,它允许你干预“内存对齐”。如果你想了解更加底层的秘密,“内存对齐”对你就不应该再透明了。

那么为什么上面程序的运行结果会是这个样子?先简单介绍一下内存对齐的原因:

  1. 平台原因:不是所有的硬件平台都能访问任意地址上的任意的数据,某些平台只能在某些地方读取某些数据,否则会抛出硬件异常。

  2. 性能原因:数据结构(尤其是栈)应该尽可能的在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要做两次内存访问;而对齐的内存访问只需要一次(经过内存对齐后,CPU访问内存的速度会大大提高)。

对原因2进行解释:

结构体内存对齐_结构体 内存对齐 数据类型 内存大小_02

CPU把内存看成是一块一块的,以2(short),4(int),8(double)为单位进行读取的。简单举一个例子:

假设CPU要读取一个4字节大小的数据到寄存器中,分两种情况讨论:

  1. 数据从0字节开始;

  2. 数据从1字节开始;

  3. 两种情况如图所示:

    结构体内存对齐_结构体 内存对齐 数据类型 内存大小_03

当CPU从0字节开始读取的时候,只需要读取一次即可把4字节的内容读取到寄存器中;当CPU从1字节开始读取的时候,显然问题开始变得复杂,因为此时数据不再内存读取边界上,这是一类内存未对齐的数据。

此时内存先访问一次内存,读取0-3字节数据到寄存器,并且再次读取4-7字节数据到寄存器中,接着把0字节和6,7,8字节的内容删掉,然后合并1,2,3,4,字节的内容3寄存器,显然相比第一种情况而言,CPU进行了很多额外的操作,大大降低了CPU的性能。


结构体对齐规则

 1.     第一个成员在结构体变量偏移量为0的地址处;

 2.    其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处;

        //对齐数 = 编译器默认的一个对齐数与该成员的较小值;

        VS中默认的值为8

        LINUX中的默认值为4

3.结构体总大小为最大对齐数(每个变量除了第一个成员都有一个对齐数,不包含默认对齐数的     整数倍;

 4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体     大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。


下面开始分析本文开头时的代码:

在结构体testType中最大数据成员的长度是8字节,以8字节为对齐系数,first占1字节,后面会有7字节的空间,编译器会填充3字节,second占4字节,所以second会填充到first后面,third占8字节,因此mytest的大小是4+4+8=16字节。

分析上面结构体数据成员的地址,根据地址我们显然可以得出此结果完全符合上面的分析。

调整文章开头结构体里面的变量顺序;

struct testType
{
    char first;
    double second;
    int third;
};

并将该程序在VS2013下运行,输出结果:

mytest地址是:bcfac8
char变量地址是:bcfac8
double变量地址是:bcfad0
int变量地址是:24

分析上面代码:

在结构体testType中最大数据成员的长度是8字节,以8字节为对齐系数,first占1字节,后面会有7字节的空间,double占8字节,编译器会填充char后面的7字节空间,int占4字节,编译器会将其填充到8字节,所以mytest的大小是3*8=24字节。

分析上面结构体数据成员的地址,根据地址我们显然可以得出此结果完全符合上面的分析。