實驗:使用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后內存
顯然我們的猜測是正確的!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM