理解C++變數儲存模型
阿新 • • 發佈:2018-12-27
在理解程式記憶體一文中我們介紹了普通程式執行時在記憶體中的佈局,下面我們專門針對C++原始碼以WinDbg為工具分析下C++程式的變數儲存模型, 要理解下面的知識,請先看懂理解程式記憶體一文。
下面我們嘗試分析C++變數的儲存模型, 我們的測試程式非常簡單:
#include <iostream>
usingnamespace std;
const char* global_const_string = "hello world";int global_int = 20;static int global_static_int = 30;int main() {static int local_static_int = 100;int local_int = 200;int* pValue = new int(300);cout << global_const_string << global_int << global_static_int << local_static_int << local_int << *pValue;delete pValue;system("pause");return 0;}
可以看到我們上面對程式雖然簡單,但是基本包括了所有的變數型別,包括靜態的,常量的,全域性的,本地的,還有new出來的,下面我們依次分析每個變數所屬的儲存區域。
我們直接用WinDbg以原始碼的方式除錯我們的測試程式consoleTest.exe.
首先我們分析下consoleTest.exe模組的起始地址及內部資料節的分佈情況, 通過!address命令:
* 400000 401000 1000 MEM_IMAGE MEM_COMMIT PAGE_READONLY Image "ConsoleTest.exe"|- 401000 41d000 1c000 MEM_IMAGE MEM_COMMIT PAGE_EXECUTE_READ Image "ConsoleTest.exe"|- 41d000 422000 5000 MEM_IMAGE MEM_COMMIT PAGE_READONLY Image "ConsoleTest.exe"|- 422000 426000 4000 MEM_IMAGE MEM_COMMIT PAGE_WRITECOPY Image "ConsoleTest.exe"|- 426000 427000 1000 MEM_IMAGE MEM_COMMIT PAGE_READONLY Image "ConsoleTest.exe"可以看到consoleTest.exe模組在記憶體中的起始地址是0x400000, 接下來可以通過!dh 0x400000分析它內部的資料節分佈, 並且最終我們可以得出如下結論:
地址 400000 - 401000 : PE檔案頭,屬性是隻讀
地址 401000 - 41d000 : .text, 屬性是隻讀可執行,表示程式碼節
地址 41d000 - 422000 : .rdata, 屬性是隻讀, 表示只讀資料
地址 422000 - 426000 : .data, 屬性是寫入時拷貝,表示可讀寫資料
地址 426000 - 427000 : .rsrc, 屬性是隻讀,表示資源節
通過!address -f:stack命令我們可以看到:
0:000> !address -f:stack BaseAddr EndAddr+1 RgnSize Type State Protect Usage------------------------------------------------------------------------------------------- 40000 13d000 fd000 MEM_PRIVATE MEM_RESERVE Stack [8b0.1d0; ~0] 13d000 13e000 1000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE|PAGE_GUARD Stack [8b0.1d0; ~0] 13e000 140000 2000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE Stack [8b0.1d0; ~0]可以看到我們主執行緒的堆疊起始地址是: 13e000 - 140000
接下來我們首先分析所有全域性變數的儲存區域, 通過x consoletest!global*命令,讓偵錯程式列出所有在consoletest模組中global開頭的除錯符號:
0:000> x consoletest!global*
00422000 ConsoleTest!global_const_string = 0x0041d1dc "hello world"
00422004 ConsoleTest!global_int = 0n20
00422008 ConsoleTest!global_static_int = 0n30
004238a0 ConsoleTest!global_locale = 0x00000000通過分析我們可以看到我們的3個全域性變數global_const_string, global_int, global_static_int全都分佈在422000 - 426000之間的.data可讀寫資料節中。
而global_const_string所指向的內容0x0041d1dc "hello world"則分佈在41d000 - 422000 之間的.rdata只讀資料節中,這個結論也符合我們平時關於全域性變數儲存區域的理解。
下面我們再嘗試分析區域性變數的儲存區域,再main函式內部cout的地方設定斷點,然後讓程式執行到此, 然後輸入dv /t /i /v命令檢視所有區域性變數, 可以看到
0:000> dv /t /i /v
prv local 0042200c int local_static_int = 0n100
prv local 0013ff70 int local_int = 0n200
prv local 0013ff74 int * pValue = 0x02248ff8我們可以看到local_static_int也分佈在422000 - 426000之間的.data可讀寫資料節中, 而local_int和pValue則都儲存在13e000 - 140000之間的堆疊區域上。
而指標pValue所指向的地址0x02248ff8我們可以通過!address 0x02248ff8命令來分析, 結果是:
0:000> !address 0x02248ff8
Usage: Heap
Allocation Base: 021d0000
Base Address: 02248000
End Address: 02249000
Region Size: 00001000
Type: 00020000 MEM_PRIVATE
State: 00001000 MEM_COMMIT
Protect: 00000004 PAGE_READWRITE
More info: !heap -p 0x21d1000
More info: !heap -p -a 0x2248ff8可以看到地址0x02248ff8是在堆(heap)上面。
通過上面的分析,我們驗證了平時C++書上關於各種型別變數儲存區域的假設,簡單來說就是全域性變數和靜態變數會被編譯到可執行檔案的資料節(分只讀和可讀寫)中, 非靜態的區域性變數則分配在堆疊(stack)上,而new(malloc)出來的記憶體則分配在堆(heap)上。 posted on 2012-09-20 21:57 Richard Wei 閱讀(2176) 評論(0) 編輯 收藏 引用 所屬分類: C++
下面我們嘗試分析C++變數的儲存模型, 我們的測試程式非常簡單:
#include <iostream>
usingnamespace std;
const char* global_const_string = "hello world";int global_int = 20;static int global_static_int = 30;int main() {static int local_static_int = 100;int local_int = 200;int* pValue = new int(300);cout << global_const_string << global_int << global_static_int << local_static_int << local_int << *pValue;delete pValue;system("pause");return 0;}
可以看到我們上面對程式雖然簡單,但是基本包括了所有的變數型別,包括靜態的,常量的,全域性的,本地的,還有new出來的,下面我們依次分析每個變數所屬的儲存區域。
我們直接用WinDbg以原始碼的方式除錯我們的測試程式consoleTest.exe.
首先我們分析下consoleTest.exe模組的起始地址及內部資料節的分佈情況, 通過!address命令:
* 400000 401000 1000 MEM_IMAGE MEM_COMMIT PAGE_READONLY Image "ConsoleTest.exe"|- 401000 41d000 1c000 MEM_IMAGE MEM_COMMIT PAGE_EXECUTE_READ Image "ConsoleTest.exe"|- 41d000 422000 5000 MEM_IMAGE MEM_COMMIT PAGE_READONLY Image "ConsoleTest.exe"|- 422000 426000 4000 MEM_IMAGE MEM_COMMIT PAGE_WRITECOPY Image "ConsoleTest.exe"|- 426000 427000 1000 MEM_IMAGE MEM_COMMIT PAGE_READONLY Image "ConsoleTest.exe"可以看到consoleTest.exe模組在記憶體中的起始地址是0x400000, 接下來可以通過!dh 0x400000分析它內部的資料節分佈, 並且最終我們可以得出如下結論:
地址 400000 - 401000 : PE檔案頭,屬性是隻讀
地址 401000 - 41d000 : .text, 屬性是隻讀可執行,表示程式碼節
地址 41d000 - 422000 : .rdata, 屬性是隻讀, 表示只讀資料
地址 422000 - 426000 : .data, 屬性是寫入時拷貝,表示可讀寫資料
地址 426000 - 427000 : .rsrc, 屬性是隻讀,表示資源節
通過!address -f:stack命令我們可以看到:
0:000> !address -f:stack BaseAddr EndAddr+1 RgnSize Type State Protect Usage------------------------------------------------------------------------------------------- 40000 13d000 fd000 MEM_PRIVATE MEM_RESERVE Stack [8b0.1d0; ~0] 13d000 13e000 1000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE|PAGE_GUARD Stack [8b0.1d0; ~0] 13e000 140000 2000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE Stack [8b0.1d0; ~0]可以看到我們主執行緒的堆疊起始地址是: 13e000 - 140000
接下來我們首先分析所有全域性變數的儲存區域, 通過x consoletest!global*命令,讓偵錯程式列出所有在consoletest模組中global開頭的除錯符號:
00422000 ConsoleTest!global_const_string = 0x0041d1dc "hello world"
00422004 ConsoleTest!global_int = 0n20
00422008 ConsoleTest!global_static_int = 0n30
004238a0 ConsoleTest!global_locale = 0x00000000通過分析我們可以看到我們的3個全域性變數global_const_string, global_int, global_static_int全都分佈在422000 - 426000之間的.data可讀寫資料節中。
而global_const_string所指向的內容0x0041d1dc
下面我們再嘗試分析區域性變數的儲存區域,再main函式內部cout的地方設定斷點,然後讓程式執行到此, 然後輸入dv /t /i /v命令檢視所有區域性變數, 可以看到
0:000> dv /t /i /v
prv local 0042200c int local_static_int = 0n100
prv local 0013ff70 int local_int = 0n200
prv local 0013ff74 int * pValue = 0x02248ff8我們可以看到local_static_int也分佈在422000 - 426000之間的.data可讀寫資料節中, 而local_int和pValue則都儲存在13e000 - 140000之間的堆疊區域上。
而指標pValue所指向的地址0x02248ff8我們可以通過!address
0:000> !address 0x02248ff8
Usage: Heap
Allocation Base: 021d0000
Base Address: 02248000
End Address: 02249000
Region Size: 00001000
Type: 00020000 MEM_PRIVATE
State: 00001000 MEM_COMMIT
Protect: 00000004 PAGE_READWRITE
More info: !heap -p 0x21d1000
More info: !heap -p -a 0x2248ff8可以看到地址0x02248ff8是在堆(heap)上面。
通過上面的分析,我們驗證了平時C++書上關於各種型別變數儲存區域的假設,簡單來說就是全域性變數和靜態變數會被編譯到可執行檔案的資料節(分只讀和可讀寫)中, 非靜態的區域性變數則分配在堆疊(stack)上,而new(malloc)出來的記憶體則分配在堆(heap)上。 posted on 2012-09-20 21:57 Richard Wei 閱讀(2176) 評論(0) 編輯 收藏 引用 所屬分類: C++