C ++编译器如何合并相同的字符串文字

时间:2011-06-08 16:06:10

标签: c++ visual-c++ compiler-construction linker

编译器(MS Visual C ++ 2010)如何在不同的cpp源文件中组合相同的字符串文字?例如,如果我分别在src1.cpp和src2.cpp中有字符串文字“hello world \ n”。编译的exe文件在constant / readonly部分中可能只有1个“hello world”字符串文字。此任务是由链接器完成的吗?

我希望实现的是我得到了一些用汇编编写的模块供C ++模块使用。这些汇编模块包含许多长字符串文字定义。我知道字符串文字与C ++源代码中的其他字符串文字相同。如果我将我的程序集生成的obj代码与编译器生成的obj代码链接,那么这些字符串文字是否会被链接器合并以删除冗余字符串,就像所有模块都在C ++中一样?

6 个答案:

答案 0 :(得分:9)

(注意以下内容仅适用于MSVC)

我的第一个答案是误导,因为我认为字面合并是链接器完成的魔术(并且只有链接器才需要/GF标志)。

然而,这是一个错误。事实证明链接器在合并字符串文字方面几乎没有特别的参与 - 当发生/GF选项给编译器时,它会将字符串文字放在目标文件的“COMDAT”部分中,并带有对象名称这是基于字符串文字的内容。因此,编译步骤需要/GF标志,而不是链接步骤。

当您使用/GF选项时,编译器将每个字符串文字放在一个单独的部分中作为COMDAT对象放在目标文件中。具有相同名称的各种COMDAT对象将由链接器折叠(我不完全确定COMDAT的语义,或者如果具有相同名称的对象具有不同的数据,链接器可能会执行的操作)。所以包含

的C文件
char* another_string = "this is a string";

在目标文件中会出现以下内容:

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变量名称连接到文字数据。

请注意,字符串文字对象的名称显然是基于文字字符串的内容,但有一些错位。我怀疑任何地方都有记录(但是谁知道 - 也许有人对它进行了逆向设计)。

无论如何,如果你想要以相同的方式处理程序集文件中的文字,你需要安排文字以相同的方式放在目标文件中。老实说,我不知道汇编程序可能有什么(如果有的话)机制。将对象放在“COMDAT”部分可能非常简单 - 将对象的名称基于字符串内容(并以适当的方式修改)是另一个故事。

除非有一些专门支持这种情况的汇编指令/关键字,否则我认为你可能会失败。当然可能有一个,但我对ml.exe已经足够生疏了,不知道,快速查看ml.exe的轻薄MSDN文档没有任何跳出来。

但是,如果您愿意将sting文字放在C文件中并通过externs在汇编代码中引用它们,那么它应该可以工作。然而,这基本上是Mark Ransom在他对这个问题的评论中提倡的。

答案 1 :(得分:4)

是的,合并资源的过程由链接器完成。

如果已编译的汇编代码中的资源被正确标记为资源,则链接器将能够将它们与已编译的C代码合并。

答案 2 :(得分:3)

很大程度上取决于具体的编译器,链接器以及驱动它们的方式。例如,此代码:

// 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

您可以从中看到字符串仅在各个翻译单元中合并。

答案 3 :(得分:1)

在解析阶段处理相同翻译单元内的相同文字。编译器在标记中转换文字并将它们存储到表中(为简单起见,假设[标记ID,值])。当编译器第一次遇到文字时,该值将输入到表中。接下来的遭遇使用相同的文字。生成代码时,此值将被放入内存中,然后每次访问都会读取此单个值(除了将值放在可执行代码中多次加速执行或缩短可执行长度的情况除外)。

链接器可以合并多个转换单元中的重复文字。如果可能,将整合标记有全局访问权限的所有标识符(即,从翻译单元外部可见)。这意味着代码将只访问符号的版本。

某些构建项目将公共或全局标识符放入(资源)表中,这样可以在不更改可执行文件的情况下更改标识符。这是需要将文本翻译成不同语言的GUI的常见做法。

请注意,对于某些编译器和链接器,它们可能默认情况下不会执行合并。有些可能需要命令行开关(或选项)。检查编译器文档以了解它如何处理重复的标识符或文本字符串。

答案 4 :(得分:1)

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

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

答案 5 :(得分:1)

汇编语言没有提供任何方法直接使用像C或C ++这样的匿名字符串文字。

因此,您几乎肯定要做的是使用名称在汇编代码中定义字符串。要使用C或C ++中的那些,你想要将数组的extern声明放入一个标题,你可以#include在任何需要访问它们的文件中(在你的C ++代码中,你将使用这些名称,不是文字本身):

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

foo.h中:

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

bar.cpp

#include "foo.h"

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