实验:使用GDB查看结构体在内存中的存储方式

结构体在内存中的表示形式是怎么样的?

结构体在内存中和普通变量存储没有太大的区别。
首先我们看看,计算机如何读取普通变量:
  普通变量例如int是占据4个字节,计算机读内存的时候会从起始地址开始读,读4个字节,按照int的规则将二进制转化为整形。所以读取普通变量我们要知道起始地址和数据类型(占据长度,解读方式)。

再看看计算机如何读取结构体变量:
  结构体是自定义变量,是由多个普通变量组成的。我们读取结构体变量,实际上是读取结构体包含的数据成员。例如结构体T包含三个数据成员:char var1,int var2,long var3。计算机如果读取结构体变量 t 的数据成员var1,计算机需要知道结构体变量的地址 &t,已知这个结构体变量占据16个字节,那么从起始地址开始往后16个字节,都存储了结构体变量的数据成员。如果我们再知道数据成员var1相对于结构体起始地址的偏移,我们就可以像读取普通变量一样读取结构体数据成员。

#include<stdio.h>
#pragma pack()
#define offset(type, name) (size_t)(&(((type *)0)->name))
typedef struct Test{
    char var1;  //1
    int var2;   //4
    long var3;  //8
    char var4;  //1
}Test_t;
/*
64bit:
Test_t: 
    cxxx iiii   //在char后面填充,使得后一个变量int从对齐参数的整数倍
    llll llll 
    cxxx xxxx   //结构体总长度必须为对齐参数的整数倍,因此在结构体尾部填充。
32bit:
Test_t:
    cxxx iiii
    llll cxxx
*/

int main(int argc, char** argv){
    Test_t t1;
    t1.var1 = 'A';
    t1.var2 = 99;
    t1.var3 = 999;
    
    printf("struct->var1: %ld \n", offset(Test_t, var1));       
    printf("struct->var2: %ld \n", offset(Test_t, var2));       
    printf("struct->var3: %ld \n", offset(Test_t, var3));       
    printf("struct->var4: %ld \n", offset(Test_t, var4));       
    
    printf("struct: %ld \n", sizeof(t1));

    return 0;
}

针对上述测试代码,我使用了GDB调试工具对程序内存进行查看。想看看结构体在内存中的表现形式是怎样的。

$ gcc -g -o struct_test struct_test.c
$ gdb
(gdb) file struct_test
(gdb) start

编译,打开gdb,在gdb中加载程序,调用main函数,创建结构体变量,此时下一步是对结构体成员赋值。
进入main函数
我们在赋值前查看一下此时结构体变量t1在内存的表现是怎样的。

(gdb) print &t1     #打印结构体变量t1的地址
(gdb) x/24xb &t1    #读取24次内存,每次读一个字节,以16进制的形式打印

使用print打印了结构体变量t1的地址,通过x/<n/f/u> <addr>的形式查看程序的内存,详情见以下博文
赋值前内存
我们看到此时的内存杂乱无章。我们步进程序,给结构体变量var1赋值,再查看一次内存。

(gdb) step  #执行下一条语句
(gdb) x/24xb &t1
(gdb) x/24db &t1    #以十进制的形式打印内存

赋值变量1后内存
我们很明显发现在0x7ffffffee370的值发生了改变,也就是结构体变量t1所在地址的第一个字节被修改了。结合我们我们的赋值语句:给结构体第一个char变量赋值,我们很容易发现:结构体第一个char变量就在结构体变量t1地址0偏移的地方。十六进制的0x41转换为十进制就是65,正好是A的ascii码。
再次步进,给第二个结构体变量赋值,再次查看内存。

(gdb) step  #执行下一条语句
(gdb) x/24xb &t1

赋值变量2后内存
对比上一次的内存,我们发现不是第二个字节的值被修改,而是第五个字节的值被修改了。这是结构体存储时候做的字节对齐,给第一个char变量后填充了3个字节,避免第二个变量int横跨边界。十六进制的0x63就是十进制的99。我们知道int变量占据4个字节,因此下一个long变量存储从0x7ffffffee378开始,长度为8个字节。显然存储采用了小端存储的方式:低字节存储在内存的低地址。
下一条语句是给变量3赋值999,十进制的999对于十六进制的0x3E7,因此我们猜测下一次内存在0x7ffffffee378开始的前两个字节会被修改。

0x7ffffffee378 0xe7 0x03 0x00 0x00 ...

我们继续步进,给变量3赋值,查看内存验证我们的想法。

(gdb) step  #执行下一条语句
(gdb) x/24xb &t1

赋值变量3后内存
显然我们的猜测是正确的!

猜你喜欢

转载自www.cnblogs.com/azhao/p/12074656.html