簡體   English   中英

C++ 編譯器如何合並相同的字符串文字

[英]How Do C++ Compilers Merge Identical String Literals

編譯器(MS Visual C++ 2010)如何在不同的 cpp 源文件中組合相同的字符串文字? 例如,如果我分別在 src1.cpp 和 src2.cpp 中有字符串文字“hello world\n”。 編譯后的 exe 文件可能在常量/只讀部分中只有 1 個“hello world”字符串文字。 這個任務是由 linker 完成的嗎?

我希望實現的是,我得到了一些用匯編編寫的模塊,供 C++ 模塊使用。 這些匯編模塊包含許多長字符串文字定義。 我知道字符串文字與 C++ 源中的其他一些字符串文字相同。 如果我將我的程序集生成的 obj 代碼與編譯器生成的 obj 代碼鏈接起來,這些字符串文字是否會被 linker 合並以刪除冗余字符串,就像所有模塊都在 C++ 中一樣?

(注意以下僅適用於 MSVC)

我的第一個答案具有誤導性,因為我認為字面合並是由 linker 完成的魔術(因此只有鏈接器需要/GF標志)。

然而,這是一個錯誤。 事實證明,linker 在合並字符串文字方面幾乎沒有特別的參與 - 發生的情況是,當將/GF選項提供給編譯器時,它會將字符串文字放在 object 文件的“COMDAT”部分中,其中 ZA8CFDE6331BD59EB2ACZ 名稱基於 C字符串文字的內容。 因此,編譯步驟需要/GF標志,而不是鏈接步驟。

當您使用/GF選項時,編譯器將 object 文件中的每個字符串文字作為 COMDAT object 放在單獨的部分中。 具有相同名稱的各種 COMDAT 對象將被 linker 折疊(我不確定 COMDAT 的語義,或者如果具有相同名稱的對象具有不同的數據,linker 可能會做什么)。 所以一個 C 文件包含

char* another_string = "this is a string";

在 object 文件中會有類似下面的內容:

SECTION HEADER #3
  .rdata name
       0 physical address
       0 virtual address
      11 size of raw data
     147 file pointer to raw data (00000147 to 00000157)
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
40301040 flags
         Initialized Data
         COMDAT; sym= "`string'" (??_C@_0BB@LFDAHJNG@this?5is?5a?5string?$AA@)
         4 byte align
         Read Only

RAW DATA #3
  00000000: 74 68 69 73 20 69 73 20 61 20 73 74 72 69 6E 67  this is a string
  00000010: 00      

使用重定位表將another_string1變量名連接到文字數據。

請注意,字符串文字 object 的名稱顯然是基於文字字符串的內容,但帶有某種修飾。 修改方案已部分記錄在Wikipedia上(請參閱“字符串常量”)。

無論如何,如果您希望以相同的方式處理程序集文件中的文字,您需要安排將文字以相同的方式放置在 object 文件中。 老實說,我不知道匯編程序可能有什么(如果有的話)機制。 將 object 放在“COMDAT”部分可能很容易 - 根據字符串內容(並以適當的方式修改)獲取 object 的名稱是另一回事。

除非有一些專門支持這種情況的匯編指令/關鍵字,否則我認為您可能不走運。 當然可能有一個,但我對ml.exe已經很生疏了,根本不知道,快速瀏覽一下ml.exe的 MSDN 文檔並沒有發現任何問題。

但是,如果您願意將字符串文字放在 C 文件中並通過 extern 在您的匯編代碼中引用它們,它應該可以工作。 然而,這基本上是馬克·蘭森在他對這個問題的評論中所提倡的。

是的,合並資源的過程是由 linker 完成的。

如果已編譯的匯編代碼中的資源被正確標記為資源,則 linker 將能夠將它們與已編譯的 C 代碼合並。

很大程度上可能取決於特定的編譯器 linker 以及您如何驅動它們。 例如,這段代碼:

// s.c
#include <stdio.h>

void f();

int main() {
    printf( "%p\n", "foo" );
    printf( "%p\n", "foo" );
    f();
}

// s2.c
#include <stdio.h>

void f() {
    printf( "%p\n", "foo" );
    printf( "%p\n", "foo" );
}

當編譯為:

gcc s.c s2.c

產生:

00403024
00403024
0040302C
0040302C

從中您可以看到字符串僅在單個翻譯單元中合並。

在解析階段處理相同翻譯單元內的相同文字。 編譯器將文字轉換為標記並將它們存儲到一個表中(為簡單起見,假設為 [token ID, value])。 當編譯器第一次遇到文字時,該值被輸入到表中。 接下來的遭遇使用相同的文字。 生成代碼時,將此值放入 memory 中,然后每次訪問都讀取此單個值(除了在可執行代碼中多次放置該值可加快執行速度或縮短可執行長度的情況)。

linker 可以合並多個翻譯單元中的重復文字。 如果可能,將合並所有標記為全局訪問(即從翻譯單元外部可見)的標識符。 這意味着代碼將僅訪問符號的版本。

一些構建項目將通用或全局標識符放入(資源)表中,這允許在不更改可執行文件的情況下更改標識符。 對於需要呈現翻譯成不同語言的文本的 GUI,這是一種常見的做法。

請注意,對於某些編譯器和鏈接器,默認情況下它們可能不會執行合並。 有些可能需要命令行開關(或選項)。 檢查您的編譯器文檔以了解它如何處理重復的標識符或文本字符串。

“/GF(消除重復字符串)”

http://msdn.microsoft.com/en-us/library/s0s0asdt.aspx

匯編語言不提供任何直接處理匿名字符串文字的方法,例如 C 或 C++。

因此,您幾乎可以肯定想要做的是在您的匯編代碼中用名稱定義字符串。 To use those from C or C++, you want to put an extern declaration of the array into a header that you can #include in whatever files need access to them (and in your C++ code, you'll use the names, not the literals他們自己):

foo.asm

.model flat, c

.data
    string1 db "This is the first string", 10, 0
    string2 db "This is the second string\n", 10, 0

富.h:

extern char string1[];
extern char string2[];

酒吧.cpp

#include "foo.h"

void baz() { std:::cout << string1; }

暫無
暫無

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

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