簡體   English   中英

為什么全局變量存儲在 .data 中而不是存儲在過程映像的堆中?

[英]Why global variables are stored in .data rather than in the heap of a process image?

我正在學習 OS Fundamentals 的測試,我想到了這一點。 當我在 C 程序中聲明一個全局(或靜態)變量時,例如:

char* msg = "Hello World!\n";    

.data保留了一個字節數組, "Hello World!\\n"字符串保存在.text ,然后當程序加載到內存並開始執行時, msg var 將使用保存在.text的字符串進行初始化.text 這是發生了什么嗎? 那么,在.data保留字節而不是在堆中保留字節有什么.data 我知道在.data它們有一個靜態大小,但它們也被保留在堆中,對嗎? 為什么要分開這些東西? 在進程映像中只有堆、堆棧和代碼部分而不是更多的分數不是更有效嗎? 這不可能是因為物理內存被映射到多個虛擬地址(例如記事本的多個實例),因為這些變量是可編輯的。

提前謝謝你

這個編譯器所做的是使這個(常量)文字成為讀/寫變量。

編譯器在.text收集所有文字字符串。 當一個文字字符串在程序中多次使用時,它將只使用.text出現的一次文字。

在啟動時,它會將其復制到.data的保留空間。 這很有趣:

char msg[] = "Hello World!\n";
char *msg  = "Hello World!\n";

編譯器將第一個文字從.text復制到.data是可以的; 它正在根據用戶的說明初始化變量。

編譯器將第二個文字復制到.data是不正確的:它應該使用指向.text的文字的指針初始化*msg並且.text段應該是只讀的(由內存硬件管理,導致異常當嘗試寫入內存時)。

“全球”通常意味着“可從任何地方訪問”。

通常在匯編程序中通過將全局數據放置在固定位置來做到這一點; 然后任何需要訪問的代碼只需使用其地址直接引用它。 這是通過在 .data 段中放置全局變量來實現的; 鏈接器將為它們分配固定地址。

您可以考慮在堆中放置“全局數據”。 如果這樣做,代碼如何訪問它? 它不能,不知道數據在堆中的位置。 此類代碼知道這一點的唯一方法是將指向“全局數據”的指針作為參數傳遞(這意味着每個子例程都必須接受此指針並將其傳遞給所有被調用者;這真的很不方便),或者代碼必須知道哪里有指向代碼可以直接訪問的堆的指針(該指針必須有一個固定的地址才能找到,所以指針本身是全局數據)。 擁有這樣一個指針意味着訪問全局數據現在總是需要一個間接的,這會減慢代碼的速度。 所以,如果你這樣做,你最終會得到一個笨拙而緩慢的訪問“全局”數據的方案。 (大多數人不會將堆中分配的數據稱為“全局數據”)。

所以...全局數據放置在易於訪問的地方。 在數據段。

如果您擁有不變且不會更改的全局數據,則可以將其放在“文本”(代碼)段中。 將此類數據放在文本段中可確保在大多數現代操作系統中此類數據受到寫保護,從而強制執行“不會更改”的假設。 這有助於發現程序中的錯誤。

簡短的回答是textdata區域由加載程序初始化 C 程序的堆存儲(通常)是malloc()及其親屬返回的內容。 它不是未初始化並由操作系統根據需要提供,而不是在程序加載時提供。 這就是為什么它也被稱為動態存儲。

通過將信息從編譯后的二進制文件復制到操作系統分配給加載程序的存儲中初始化像字符串這樣的字面常量。 您提供的.text.data段名稱是已編譯的二進制文件中用於標記此類信息的約定。 不同的風格告訴操作系統副本應該如何在 VM 空間中映射 - 作為只讀、讀寫等。

我上面所說的只是典型的。 編譯器決定如何在操作系統和硬件提供的內存模型中存儲 C 值。 您的問題和預期答案僅與您使用的編譯器和操作系統相關。

char* msg = "Hello World!\n";

聲明一個初始化的全局char指針值可以通過多種方式編譯。 但最常見的是在只讀存儲(Linux .text )中分配一個文字字符串Hello World~\\n\\0 C 允許但不要求保護文字字符串,以便像這樣的代碼

char* msg = "Hello World!\n";

...
{
  ...
  char *p = msg;
  strcpy(msg, "foo"); 
  printf("%s", p);

將引發錯誤(通常是段違規)而不是打印foo 並非所有操作系統/編譯器配置都會這樣做。 例如,嵌入式系統可能不支持只讀存儲,因此上面的代碼可能會運行,但在它運行之后,C 標准並沒有指定任何關於程序將執行的操作。

編譯器需要做的另一件事是分配和初始化msg的全局值。 這(通常)是 2 字節、4 字節或 8 字節的指針,同樣取決於編譯器、操作系統和硬件。 它必須設置為文字字符串的地址。 執行此操作的常識方法是將符號發送到二進制文件中,允許加載程序在程序啟動時注意讀寫存儲 (Linux .data ) 中的初始化。 但還有其他選擇:固定偏移和啟動代碼是兩個。

在運行時執行的具有靜態存儲持續時間 (SSD) 的任何類型的對象初始化通常稱為動態初始化(借用 C++ 術語)。 SSD 對象的動態初始化,無論它多么有用,都會帶來許多問題。 例如,這種初始化的最終結果可能取決於它的執行順序。 由於某些不可預測的原因(內存不足?),初始化可能會失敗。 而且,很明顯,SSD 對象的值不能被視為常量表達式。

同時,為了避免此類問題,有意識地設計了 C 語言規范,以避免對 SSD 對象進行任何動態初始化。 例如,C 程序中的所有此類對象都需要使用常量值進行初始化。 這意味着此類對象的初始值不受任何初始化順序依賴性的影響,並且在概念上“在編譯時已知”。 當 C 程序啟動時,所有 SSD 對象都以正確的預分配值開始它們的生命周期,不需要額外的運行時初始化。

然而,實際情況更為復雜,在編譯時並不總是能夠預測 SSD 對象的初始值。 例如,描述可重定位對象位置的地址常量僅在加載時才知道,而不是在編譯時,這通常使得在編譯時無法預測 SSD 指針的值。 語言規范旨在適應這些特性。 C 中地址常量的屬性被故意限制,以便稍后在加載時初始化它們的值。 在 C 中,當初始值在編譯時未知的情況下,這被認為是一種適當的折衷,但我們仍然希望保持 SSD 對象在預初始化狀態下開始生命的外觀。

請注意,語言規范不要求以這種方式專門實現它。 它說

所有具有靜態存儲持續時間的對象都應在程序啟動前進行初始化(設置為其初始值)。 否則未指定此類初始化的方式和時間。

這意味着很有可能提出一個有效的實現,它將使用堆內存為 SSD 對象分配存儲並滿足語言規范的所有其他要求。 但是從歷史上看,C 實現通常努力避免運行時初始化,並且更喜歡依賴初始化的數據段(如.data )和一些基本的加載器功能。 同樣,語言規范是特意針對這種 SSD 對象支持方法量身定制的。

.data只需要一個指針(指針變量msg )的空間, "Hello, world!" 字符串繼續在.text段中,任何地方都沒有復制任何內容。 msg變量(存儲在.data段中的指針)只是指向.text段中字符數組所在位置的指針(該數組只讀)。 只需嘗試修改數組中的某些內容,例如, msg[3] = '\\0'; 看看它是如何失敗的(盡管msgchar *而不是const char *

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM