實驗:使用GDB查看結構體在內存中的存儲方式

結構體在內存中的表示形式是怎麼樣的?

結構體在內存中和普通變量存儲沒有太大的區別。
首先咱們看看,計算機如何讀取普通變量:
  普通變量例如int是佔據4個字節,計算機讀內存的時候會從起始地址開始讀,讀4個字節,按照int的規則將二進制轉化爲整形。因此讀取普通變量咱們要知道起始地址和數據類型(佔據長度,解讀方式)。c++

再看看計算機如何讀取結構體變量:
  結構體是自定義變量,是由多個普通變量組成的。咱們讀取結構體變量,其實是讀取結構體包含的數據成員。例如結構體T包含三個數據成員:char var1,int var2,long var3。計算機若是讀取結構體變量 t 的數據成員var1,計算機須要知道結構體變量的地址 &t,已知這個結構體變量佔據16個字節,那麼從起始地址開始日後16個字節,都存儲告終構體變量的數據成員。若是咱們再知道數據成員var1相對於結構體起始地址的偏移,咱們就能夠像讀取普通變量同樣讀取結構體數據成員。shell

#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碼。
再次步進,給第二個結構體變量賦值,再次查看內存。.net

(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賦值,查看內存驗證咱們的想法。code

(gdb) step  #執行下一條語句
(gdb) x/24xb &t1

賦值變量3後內存
顯然咱們的猜想是正確的!blog

相關文章
相關標籤/搜索