我在两个不同的文件中有全局常量字符定义:
f1:
const char foo1[] = "SAME_VALUE";
f2:
const char foo2[] = "SAME_VALUE";
想了解在最终二进制文件中是否会对其进行优化以占用内存中的公共空间。这是在海湾合作委员会的背景下
答案 0 :(得分:5)
这种优化称为string interning。
GCC 默认设置 -fmerge-constants
标志:
尝试跨编译单元合并相同的常量(字符串常量和浮点常量)。
如果汇编器和链接器支持,则此选项是优化编译的默认选项。使用 -fno-merge-constants 来抑制这种行为。
在 -O、-O2、-O3、-Os 级别启用。
让我们使用名为 f.c 的第三个文件创建一个可执行文件以引用字符串:
#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;
}
当您分别在 f1.c 和 f2.c 中定义以下内容时(proposition#1):
const char foo1[] = "SAME_VALUE";
const char foo2[] = "SAME_VALUE";
这会产生 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'
但是如果你分别在 f1.c 和 f2.c (proposition#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
为了避免与命题#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'
答案 1 :(得分:2)
阅读像 n1570 这样的 C 标准。它要求 foo1 != foo2
在运行时发生该测试时(当然在 extern const char foo1[]; extern const char foo2[];
声明之后)。它可以接受编译器将 if (foo1==foo2) abort();
(或在某个 assert(foo1 != foo2);
之后的 #include <assert.h>
,参见 assert(3)...)优化为无操作。
想了解在最终二进制文件中是否会对其进行优化以占用内存中的公共空间。
也许 GCC 在编译和链接时调用 as gcc -flto -O3
(可能还有 -fwhole-program
)可以优化它。
如果内存空间在您的项目中非常重要,请考虑编写您的 GCC plugin - 或一些 GNU Binutils 扩展 - 以检测(并可能优化)这种情况。这样的插件可以使用一些 sqlite 数据库(在编译时)来管理所有全局 const char
定义。
请注意,您想要的优化要求编译器确实检测到像 foo1 == foo2
这样的指针相等性从不被测试(并且对于 const char*p1, *p2;
,{{1 }} 是 p1
,foo1
是 p2
并且指针相等性 foo2
在您的程序运行时进行测试)。您可以使用 Frama-C 之类的工具来确保这一点。
更多的编译器正在转换
p1 == p2
相当于
const char foo1[] = "SAME VALUE";
const char foo2[] = "VALUE";
我的建议是在您的构建过程中生成 C 代码,很好地记录它,并明确共享这些数据。
另一种方法是使用您的预处理器(可能高于 GNU m4 或 GPP)或编写您的 GCC plugin 定义一些内置的 const char foo1[] = "SAME VALUE";
const char foo2[] = foo1 + 5; //// since strlen("SAME ") is 5
编译器。
您稍后发表评论:
<块引用>代码是由工具/脚本生成的,并且有很多这样的常量和文件。
然后只需改进该工具/脚本即可生成更好的代码。您的 C 生成工具可以使用一些 sqlite 数据库。
附注。 AFAIK,GCC 和 Clang 都可以做这样的优化,但我不确定。由于它们是开源的,您可以改进它们。
PPS。您的问题可能是 Bismon 静态源代码分析器的一个用例,并且与 CHARIOT 和 DECODER 项目相关。这似乎比你想象的要困难。您可以联系这些项目的负责人。