结构体内存对齐

意义

  内存对齐能够很好的提高内存的访问效率,这使得能够在尽可能少的总线周期内读取到所需要的数据。

内存对齐的原则

  • 结构体变量的起始地址能够被其最宽成员所占字节数整除
  • 结构体每个成员相对于起始地址的偏移都能够被其自身所占的字节数整除,否则在上一个成员后填充直到符合的地址处放置自身的值
  • 结构体总体大小是最宽成员所占字节数的整数倍

  编译时候可以指定对齐的大小,这里只分析和测试默认情况。对于内存对齐的原则我们可以这样理解:首先,起始地址当是最宽成员字节数的整数倍时,能够整齐的读取;其次,每一个成员需要对齐到地址为自身字节数整数倍,这样极大的方便了结构体访问成员变量时查找内存位置的便捷性。

上机测试内存对齐情况

测试环境

  • 操作系统:Windows10专业版
  • 编译器版本:g++ (x86_64-posix-seh-rev0, Built by MinGW-W64 project) 8.1.0
  • 编辑器:vscode

测试思路

  我们定义一个结构体,并改变结构体成员变量的声明顺序得到另外一个版本的结构体。声明结构体变量,然后将其对应的成员变量赋予相同的值,然后通过打印内存数据,检验数据的存放方式。

测试的结构体定义

  我们测试的结构体定义,初试版本命名为test0,其中包含char、short、int、double四个类型的成员变量,各自占用的字节数为1B、2B、4B、8B,共计15B,实际占用空间应该为16B。

struct test0{
    char chara;
    short shortnum;
    int intnum;
    double doublenum;
};

  分析如下:如果我们按照内存对齐的原则对其进行排列,可以获得以下的情况:
  改变结构体中成员变量的声明顺序,我们将其声明顺序逆置,可以得到新的版本test1

struct test1{
    double doublenum;
    int intnum;
    short shortnum;
    char chara;
};

  同样的,我们对其内存中各个成员变量的存放情况分析可以得到以下情况:

测试方法的实现思路

  这里,我们使用一个打印函数来输出一段内存中的内容,给出首地址和长度,我们按照每8B一行、用空格间隔的形式按照十六进制的内容打印内存内容。把指针强制转换为unsigned char类型,然后逐个遍历该段内存空间,然后输出内容,实现的函数如下所示:

void PrintMemory(void *p,int len){                          //使用空类型指针接收参数
    unsigned char *tmp=(unsigned char*)p;                   //强制类型转换
    for(int i=0;i<len;i++){
        if(i%8==0&&i) putchar('\n');                        //每8个换行
        printf("%04X ",*(tmp+i));
    }
    putchar('\n');
    putchar('\n');                                          //控制输出格式,以实现输出间隔清晰
}

结构体成员变量初始化

  我们直接对结构体成员变量进行初始化,char型初始化为'A'short型变量初始化为1;int型初始化为INT的最大值(这里直接用C++库中的宏实现);double型初始化为1.0;这里直接用了一个函数实现,使得主函数中初始化每个变量的时候更加简洁,实现如下:

void InitMember(void *p,int type,char chara='A',short shortnum=1,
                int intnum=INT_MAX,double doublenum=1.0)    //默认参数列表,type表示结构体的类型,p为要初始化的结构体的指针
{
    switch(type){
        case 0: {                                           //不同类型做相应的指针转换,然后进行赋值
            test0* testoneptr=(test0*)p;
            testoneptr->chara=chara;
            testoneptr->shortnum=shortnum;
            testoneptr->intnum=intnum;
            testoneptr->doublenum=doublenum;
            break;
        }
        case 1: {
            test1* testtwoptr=(test1*)p;
            testtwoptr->chara=chara;
            testtwoptr->shortnum=shortnum;
            testtwoptr->intnum=intnum;
            testtwoptr->doublenum=doublenum;
            break;
        }
    }
}

输出结构体的内存数据

  首先需要一点前置工作,我们可能不太熟悉初始化使用的数值在内存中如何表达的,所以同样的我们把初始化数据的存储表示打印出来,以供参考。然后,我们使用函数打印每一个结构体的内容并做分析,代码实现如下(只需要添加必要的头文件即可编译运行这里给出的程序):

int main(void){
    test0 testzero;                                         //test0类型的结构体变量
    test1 testone;                                          //test1类型的结构体变量
    InitMember(&testzero,0);                                //初始化成员变量
    InitMember(&testone,1);
    cout<<"sizeof test0:"<<sizeof(testzero)<<endl;          //输出两个结构体所占的内存空间
    cout<<"sizeof test1:"<<sizeof(testone)<<endl;

    cout<<"\'A\'的内存数据为:\n";                            //输出初始化数据的内存表示
    char charA='A';
    PrintMemory(&charA,1);

    cout<<"short 1的内存数据为:\n";
    short shortnum=1;
    PrintMemory(&shortnum,2);

    cout<<"int INT_MAX的内存数据为:\n";
    int intnum=INT_MAX;
    PrintMemory(&intnum,4);

    cout<<"double 1.0的内存数据为:\n";
    double doublenum=1.0;
    PrintMemory(&doublenum,8);
                                                            //查看两个结构体的内存内容
    cout<<"test0 的起始地址:"<<&testzero<<" ,其内存数据为:\n";
    PrintMemory(&testzero,sizeof(testzero));
    cout<<"test1 的起始地址:"<<&testone<<" ,其内存数据为:\n";
    PrintMemory(&testone,sizeof(testone));

    return 0;
}

  输出结果如下所示:

sizeof test0:16  
sizeof test1:16  
'A'的内存数据为:
0041

short 1的内存数据为:
0001 0000

int INT_MAX的内存数据为:
00FF 00FF 00FF 007F

double 1.0的内存数据为:
0000 0000 0000 0000 0000 0000 00F0 003F

test0 的起始地址:0x62fe10 ,其内存数据为:
0041 0019 0001 0000 00FF 00FF 00FF 007F
0000 0000 0000 0000 0000 0000 00F0 003F

test1 的起始地址:0x62fe00 ,其内存数据为:
0000 0000 0000 0000 0000 0000 00F0 003F
00FF 00FF 00FF 007F 0001 0000 0041 0000

  对内容分析如下:
  可以看到,实际输出的结果和我们预期分析的一摸一样,这便是内存对齐机制的实际体现。

更上一层楼

  如果我们给新版本的结构体再多增加两个char类型的成员,会怎么样呢?这样上边给出的输出结果的最后一个位置的填充将会被第二个char字符填充,第三个char字符将另起一行,因为要对齐到最宽成员字节数的整数倍,这导致不得不继续填充凑齐为24B的大小,稍作改动,完整的程序如下,运行测试如下:

#include<bits/stdc++.h>
using namespace std;
struct test0{
    char chara;
    short shortnum;
    int intnum;
    double doublenum;
};
struct test1{
    double doublenum;
    int intnum;
    short shortnum;
    char chara;
    char chara1;
    char chara2;
};
void PrintMemory(void *p,int len){
    unsigned char *tmp=(unsigned char*)p;
    for(int i=0;i<len;i++){
        if(i%8==0&&i) putchar('\n');
        printf("%04X ",*(tmp+i));
    }
    putchar('\n');
    putchar('\n');
}
void InitMember(void *p,int type,char chara='A',short shortnum=1,
                int intnum=INT_MAX,double doublenum=1.0)
{
    switch(type){
        case 0: {
            test0* testoneptr=(test0*)p;
            testoneptr->chara=chara;
            testoneptr->shortnum=shortnum;
            testoneptr->intnum=intnum;
            testoneptr->doublenum=doublenum;
            break;
        }
        case 1: {
            test1* testtwoptr=(test1*)p;
            testtwoptr->chara=testtwoptr->chara1=testtwoptr->chara2=chara;      //新版本的结构体字符都初始化为A
            testtwoptr->shortnum=shortnum;
            testtwoptr->intnum=intnum;
            testtwoptr->doublenum=doublenum;
            break;
        }
    }
}
int main(void){
    test0 testzero;
    test1 testone;
    InitMember(&testzero,0);
    InitMember(&testone,1);
    cout<<"sizeof test0:"<<sizeof(testzero)<<endl;
    cout<<"sizeof test1:"<<sizeof(testone)<<endl;

    cout<<"\'A\'的内存数据为:\n";
    char charA='A';
    PrintMemory(&charA,1);

    cout<<"short 1的内存数据为:\n";
    short shortnum=1;
    PrintMemory(&shortnum,2);

    cout<<"int INT_MAX的内存数据为:\n";
    int intnum=INT_MAX;
    PrintMemory(&intnum,4);

    cout<<"double 1.0的内存数据为:\n";
    double doublenum=1.0;
    PrintMemory(&doublenum,8);

    cout<<"test0 的起始地址:"<<&testzero<<" ,其内存数据为:\n";
    PrintMemory(&testzero,sizeof(testzero));
    cout<<"test1 的起始地址:"<<&testone<<" ,其内存数据为:\n";
    PrintMemory(&testone,sizeof(testone));

    return 0;
}

  输出结果如下:

sizeof test0:16
sizeof test1:24
'A'的内存数据为:
0041

short 1的内存数据为:
0001 0000

int INT_MAX的内存数据为:
00FF 00FF 00FF 007F

double 1.0的内存数据为:
0000 0000 0000 0000 0000 0000 00F0 003F

test0 的起始地址:0x62fe10 ,其内存数据为:
0041 0019 0001 0000 00FF 00FF 00FF 007F
0000 0000 0000 0000 0000 0000 00F0 003F

test1 的起始地址:0x62fdf0 ,其内存数据为:
0000 0000 0000 0000 0000 0000 00F0 003F
00FF 00FF 00FF 007F 0001 0000 0041 0041
0041 0000 0000 0000 0000 0000 0000 0000

  大家可以根据输出结果,自行分析。


当珍惜每一片时光~