2011-09-20 81 views
2

請閱讀以下C++代碼和結果。根據某些wiki頁面,靜態,自動和動態分配的變量分配在不同的地址空間中,即數據段,堆棧和堆。但是,在我看來,靜態和動態變量的地址大致在同一個地方。爲什麼?我如何知道靜態變量確實在數據段中,而不是在堆中?關於C++中地址空間的問題

更廣泛的問題是,在C++中是否可以知道每個地址空間的範圍(或可用大小)?

我的另一個問題是爲什麼volatile變量的地址是1?

#include <iostream> 
using namespace std; 
static int i; 
int main() { 
    cout << sizeof(int*) << endl; 
    int j; 
    int* k = new int[10]; 
    volatile int l; 

    cout << &i << endl; 
    cout << &j << endl; 
    cout << k << endl; 
    cout << &l << endl; 

    delete[] k; 
} 

結果:

8 
0x1000010e4 
0x7fff5fbff66c 
0x100100080 
1 
+5

每個進程都有*一個*地址空間。你正在混合地址空間的部分/段。 –

+0

然後我的問題變成:我們可以用C++知道每個段(或段)的範圍(或大小)是什麼? –

+1

如果你的可修改全局變量接近用new分配的對象,那可能是因爲加載器或程序自己的init代碼(在調用'main'之前)爲它們分配了一塊內存,並且該塊最終來了來自與'new'使用相同的來源。 –

回答

8

只有操作系統可以告訴你哪些部分位於地址空間的哪一部分。如果你在程序中使用Linux,從,輸出的/proc/self/maps內容:

我在你的程序的末尾添加

std::ifstream maps("/proc/self/maps"); 
std::cout << maps.rdbuf(); 

,並印刷:

8 
0x6021c0 
0x7fffe07f60bc 
0x603010 
1 
... 
00601000-00602000 r--p 00001000 09:01 9175691 /home/cubbi/test 
       ^-- read-only static data 
00602000-00603000 rw-p 00002000 09:01 9175691 /home/cubbi/test 
        ^^ -- writeable static data 
00603000-00624000 rw-p 00000000 00:00 0   [heap] 
... 
7fffe07d7000-7fffe07f9000 rw-p 00000000 00:00 0 [stack] 

作爲用於打印volatile int的地址,沒有標準operator<<,需要一個指針到揮發性-T,但有一個取bool,任何指針可以被隱式轉換爲void*,WH然後可以將其轉換爲bool。要打印您需要的地址,把上面一行

cout << const_cast<int*>(&l) << endl; 
4

你的變量都將在同一個地址空間,顧名思義。

但是它們可能在不同的部分(或部分),這取決於它們是靜態的,本地的(自動的)還是動態的。

4

這都是非常依賴於平臺的,但是這裏是我的理解:有三個相關的段,文本,數據和堆棧。文本段包含代碼。數據段包含靜態變量。堆棧段包含堆和棧,並且他們填寫從相對端堆棧段:

| Text | Data | ---> Heap  Stack <--- | 
      i k       j 

由於數據大小是在編譯時已知的,我想,堆棧段將右後它遵循,這樣第一個堆分配應該在最後一個靜態變量之後。另一方面,第一堆棧分配儘可能遠,由堆棧段的大小決定。

3

您在詢問您的進程的內存映像或內存段的順序和位置。這在不同的執行環境(WIN32內存映射必然不同於Linux內存映射),不同版本(XP內存映射可能不同於Windows7內存映射)和不同CPU模式(顯然是x86和x86- 64個不同)。

即使所有其他變量相同,內存映射甚至可能會不同於同一程序的運行。

有關您的環境的更多詳細信息,您可以google「win32內存映射」和「linux進程內存映射」。 (注意:這與「存儲器映射文件」不同。)

不可能(以任何便攜的方式)確定各種存儲器的範圍和大小(甚至數量或存在)段。 C++沒有要求,例如,靜態數據地址不與動態數據地址交錯。對於Linux,請查閱pmap command。對於Windows,請嘗試使用sysinternals工具之一。

最後,你的volatile變量的地址其實不是,1,而是std::cout就是這樣打印的。有關更多信息,請參閱Why does std::cout convert volatile pointers to bool?

1

的語言允許變量被放置在不同的存儲區域不要求任何特定的編譯器/操作系統實際上使這些地區分離的事實而不同(編譯器必須遵循正確的語言規則,例如,通過new分配的內存必須由delete被釋放,雖然它可以是物理上接近的內存分配等方式)。例如,通過malloc(從堆正式分配的)分配的存儲器和存儲器之間的Standard makes a distinction通過new(從自由存儲區分配正式)分配;但我從來沒有見過一個實現把它們放在不同的內存段中。

我的另一個問題是爲什麼volatile變量的地址是1?

變量的地址不是1。然而,std::iostream(例如,std::cout)不具有用於volatile指針的過載,並且最接近的合法過載是用於bool。所以輸出volatile指針將輸出0(如果指針是NULL)或1的(所有其他情況下)中的地址。

一個更廣泛的問題是,在C++中是否有可能知道每個地址空間的範圍(或可用大小)?

它可能在特定的平臺上,但沒有任何跨平臺的方式來做到這一點。

0

您可能會看到的數據和堆棧段更好地分離,如果你犯了一個更大的堆棧,用遞歸調用。正如其他人所指出的,它們是相同的「地址空間」的組成部分,但通常在不同的街區。

#include <iostream> 

using namespace std; 
int print_addresses(int depth) { 
    int j; 
    int* k = new int[10]; 
    volatile int l; 
    static int i; 

    cout << "&i = " << &i << " " 
    << "&j = " << &j << " " 
    << "k = " << k << " " 
    << "&l = " << (int *)&l 
    << endl; 


    if (depth < 10) 
     print_addresses(depth + 1); 
    delete[] k; 
} 


int main() { 
    cout << sizeof(int*) << endl; 
    print_addresses(0); 
} 

在x86-64 linux機器上,我得到以下輸出。它顯示靜態變量具有相同的地址。堆棧變量增加的地址,因爲我賺更多的函數調用,但堆中分配的變量的地址減少。這種行爲是編譯器和平臺特定的,所以你不應該依賴這種行爲。您可以通過將其指針轉換爲int指針來獲取volatile的地址。

sizeof(int*)=8 
&i = 0x6011a4 &j = 0x7fff9f67bb8c k = 0x15e7010 &l = 0x7fff9f67bb88 
&i = 0x6011a4 &j = 0x7fff9f67bb5c k = 0x15e7040 &l = 0x7fff9f67bb58 
&i = 0x6011a4 &j = 0x7fff9f67bb2c k = 0x15e7070 &l = 0x7fff9f67bb28 
&i = 0x6011a4 &j = 0x7fff9f67bafc k = 0x15e70a0 &l = 0x7fff9f67baf8 
&i = 0x6011a4 &j = 0x7fff9f67bacc k = 0x15e70d0 &l = 0x7fff9f67bac8 
&i = 0x6011a4 &j = 0x7fff9f67ba9c k = 0x15e7100 &l = 0x7fff9f67ba98 
&i = 0x6011a4 &j = 0x7fff9f67ba6c k = 0x15e7130 &l = 0x7fff9f67ba68 
&i = 0x6011a4 &j = 0x7fff9f67ba3c k = 0x15e7160 &l = 0x7fff9f67ba38 
&i = 0x6011a4 &j = 0x7fff9f67ba0c k = 0x15e7190 &l = 0x7fff9f67ba08 
&i = 0x6011a4 &j = 0x7fff9f67b9dc k = 0x15e71c0 &l = 0x7fff9f67b9d8 
&i = 0x6011a4 &j = 0x7fff9f67b9ac k = 0x15e71f0 &l = 0x7fff9f67b9a8 
1

在函數中分配的局部變量佔用堆棧空間(否則遞歸函數將不起作用)。但

int* k = new int[10]; 

將在某個時候調用malloc,因爲「新」,從全局的存儲分配。所以*我在你的代碼中雖然是局部變量,但指向全局空間。

具有文件範圍的全局變量和變量(不在函數中的變量 - 例如'static int i')在數據段中(或者在沒有定義任何值且與平臺相關的情況下稱爲bss)。在某些平臺上,小塊數據位於代碼段。數據部分是文件的一部分。