Kar*_*run 3 c compiler-optimization string-interning
我在两个不同的文件中有全局常量字符定义:
f1:
const char foo1[] = "SAME_VALUE";
Run Code Online (Sandbox Code Playgroud)
f2:
const char foo2[] = "SAME_VALUE";
Run Code Online (Sandbox Code Playgroud)
想了解在最终的二进制文件中是否会对其进行优化以占用内存中的公共空间。这是在海湾合作委员会的背景下
这种优化称为字符串实习。
GCC-fmerge-constants默认设置标志:
尝试跨编译单元合并相同的常量(字符串常量和浮点常量)。
如果汇编器和链接器支持,则此选项是优化编译的默认选项。使用 -fno-merge-constants 来抑制这种行为。
在 -O、-O2、-O3、-Os 级别启用。
让我们使用名为fc的第三个文件创建一个可执行文件来引用字符串:
#include <stdio.h>
// For proposition#1
extern const char foo1[], foo2[];
// For proposition#2
//extern const char *foo1, *foo2;
int main(void) {
printf("%s\n", foo1);
printf("%s\n", foo2);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
当您分别在f1.c和f2.c 中定义以下内容时(命题#1):
const char foo1[] = "SAME_VALUE";
Run Code Online (Sandbox Code Playgroud)
const char foo2[] = "SAME_VALUE";
Run Code Online (Sandbox Code Playgroud)
这会产生 2 个不同的存储空间,其中存储了字符串“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'
Run Code Online (Sandbox Code Playgroud)
但是如果你分别在f1.c和f2.c 中定义以下(命题#2):
const char *foo1 = "SAME_VALUE";
Run Code Online (Sandbox Code Playgroud)
const char *foo2 = "SAME_VALUE";
Run Code Online (Sandbox Code Playgroud)
您定义了两个指向同一字符串的指针。在这种情况下,“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
Run Code Online (Sandbox Code Playgroud)
为了避免与命题#1重复,GCC 提供-fmerge-all-constants:
尝试合并相同的常量和相同的变量。
此选项意味着 -fmerge-constants。除了 -fmerge-constants 之外,这还考虑了例如常量初始化数组或具有整数或浮点类型的初始化常量变量。C 或 C++ 等语言要求每个变量(包括递归调用中同一变量的多个实例)具有不同的位置,因此使用此选项会导致不一致的行为。
让我们用这个标志重建命题#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'
Run Code Online (Sandbox Code Playgroud)