这种优化被称为
string interning。
GCC默认设置
-fmerge-constants
标志:
尝试在编译单元中合并相同的常量(字符串常量和浮点常量)。
如果汇编器和链接器支持,则此选项是优化编译的默认设置。使用-fno-merge-constants来禁止此行为。
在级别-O,-O2,-O3,-Os下启用。
让我们制作一个可执行文件,其中包含一个名为
f.c的第三个文件来引用这些字符串:
#include <stdio.h>
extern const char foo1[], foo2[];
int main(void) {
printf("%s\n", foo1);
printf("%s\n", foo2);
return 0;
}
当你在
f1.c 和
f2.c 中分别定义以下内容时(
命题#1):
const char foo1[] = "SAME_VALUE";
const char foo2[] = "SAME_VALUE";
这会导致字符串"SAME_VALUE"存储在两个不同的内存空间中。因此,该字符串被复制了。
$ gcc f.c f1.c f2.c
$ objdump -D a.out
[...]
0000000000001060 <main>:
1060: f3 0f 1e fa endbr64
1064: 48 83 ec 08 sub $0x8,%rsp
1068: 48 8d 3d 99 0f 00 00 lea 0xf99(%rip),%rdi <-- foo1@2008
106f: e8 dc ff ff ff callq 1050 <puts@plt>
1074: 48 8d 3d 9d 0f 00 00 lea 0xf9d(%rip),%rdi <-- foo2@2018
107b: e8 d0 ff ff ff callq 1050 <puts@plt>
[...]
0000000000002008 <foo1>:
2008: 53 'S' <-- 1 string @ 2008
2009: 41 'A'
200a: 4d 'M'
200b: 45 5f 'E' '_'
200d: 56 'V'
200e: 41 'A'
200f: 4c 55 'L' 'U'
2011: 45 'E'
...
0000000000002018 <foo2>:
2018: 53 'S' <-- Another string @ 2018
2019: 41 'A'
201a: 4d 'M'
201b: 45 5f 'E' '_'
201d: 56 'V'
201e: 41 'A'
201f: 4c 55 'L' 'U'
2021: 45 'E'
但是如果你在
f1.c和
f2.c中分别定义以下内容(
命题#2):
const char *foo1 = "SAME_VALUE";
const char *foo2 = "SAME_VALUE";
你定义了两个指针,它们将指向同一个字符串。在这种情况下,“SAME_VALUE”可能不会被复制。在以下原始反汇编中,该字符串位于地址2004处,
foo1和
foo2都指向它:
$ gcc f.c f1.c f2.c
$ objdump -D a.out
[...]
2004: 53 'S' <-- 1 string @ 2004
2005: 41 'A'
2006: 4d 'M'
2007: 45 5f 'E' '_'
2009: 56 'V'
200a: 41 'A'
200b: 4c 55 'L' 'U'
200d: 45 'E'
[...]
0000000000001060 <main>:
1060: f3 0f 1e fa endbr64
1064: 48 83 ec 08 sub $0x8,%rsp
1068: 48 8b 3d a1 2f 00 00 mov 0x2fa1(%rip),%rdi <-- 106f+2fa1=foo1@4010
106f: e8 dc ff ff ff callq 1050 <puts@plt>
1074: 48 8b 3d 9d 2f 00 00 mov 0x2f9d(%rip),%rdi <-- 107b+2f9d=foo2@4018
[...]
0000000000004010 <foo1>:
4010: 04 20 <-- foo1 = @2004
[...]
0000000000004018 <foo2>:
4018: 04 20 <-- foo2 = @2004
为避免与proposition#1重复,GCC提供了-fmerge-all-constants选项:
尝试合并相同的常量和相同的变量。此选项意味着-fmerge-constants。除了-fmerge-constants之外,还考虑了例如具有整数或浮点类型的初始化常量数组或初始化常量变量等内容。像C或C++这样的语言要求每个变量(包括递归调用中相同变量的多个实例)都具有不同的位置,因此使用此选项会导致不符合规范的行为。
让我们使用此标志重新构建proposition#1。我们可以看到foo2被优化掉了,只保留并引用了foo1。
$ gcc -fmerge-all-constants f.c f1.c f2.c
$ objdump -D a.out
[...]
0000000000001149 <main>:
1149: f3 0f 1e fa endbr64
114d: 55 push %rbp
114e: 48 89 e5 mov %rsp,%rbp
1151: 48 8d 3d b0 0e 00 00 lea 0xeb0(%rip),%rdi <-- 1158(RIP) + eb0 = 2008 <foo1>
1158: e8 f3 fe ff ff callq 1050 <puts@plt>
115d: 48 8d 3d a4 0e 00 00 lea 0xea4(%rip),%rdi <-- 1164(RIP) + ea4 = 2008 <foo1>
1164: e8 e7 fe ff ff callq 1050 <puts@plt>
1169: b8 00 00 00 00 mov $0x0,%eax
[...]
0000000000002008 <foo1>:
2008: 53 'S' <--- foo2 optimized out, only foo1 defined
2009: 41 'A'
200a: 4d 'M'
200b: 45 5f 'E' '_'
200d: 56 'V'
200e: 41 'A'
200f: 4c 55 'L' 'U'
2011: 45 'E'
const
关键字。这不是char * foo
与char foo[]
的比较,前者是常量,后者是可修改的。问题是关于const char foo[]
:const
关键字是否足以让编译器理解它可以被视为char *foo
? - Roberto Caboni