結構體在內存中的表示形式是怎么樣的?
結構體在內存中和普通變量存儲沒有太大的區別。
首先我們看看,計算機如何讀取普通變量:
普通變量例如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函數,創建結構體變量,此時下一步是對結構體成員賦值。
我們在賦值前查看一下此時結構體變量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 #以十進制的形式打印內存
我們很明顯發現在0x7ffffffee370的值發生了改變,也就是結構體變量t1所在地址的第一個字節被修改了。結合我們我們的賦值語句:給結構體第一個char變量賦值,我們很容易發現:結構體第一個char變量就在結構體變量t1地址0偏移的地方。十六進制的0x41轉換為十進制就是65,正好是A的ascii碼。
再次步進,給第二個結構體變量賦值,再次查看內存。
(gdb) step #執行下一條語句
(gdb) x/24xb &t1
對比上一次的內存,我們發現不是第二個字節的值被修改,而是第五個字節的值被修改了。這是結構體存儲時候做的字節對齊,給第一個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
顯然我們的猜測是正確的!