const char 编译器优化
const char compiler optimization
我在两个不同的文件中有全局 const char 定义:
f1:
const char foo1[] = "SAME_VALUE";
f2:
const char foo2[] = "SAME_VALUE";
想了解在最终的二进制文件中是否会对其进行优化以在内存中使用 common space。这是在 GCC
的上下文中
阅读 C 标准,如 n1570. It requires that foo1 != foo2
when that test happens at runtime (after of course extern const char foo1[]; extern const char foo2[];
declarations). It could accept that a compiler optimizes if (foo1==foo2) abort();
(or assert(foo1 != foo2);
after some #include <assert.h>
, see assert(3)...) 到无操作。
Wanted to understand if in the final binary this will be optimized to take common space in memory.
这是非常特定于编译器的。
也许 GCC invoked as gcc -flto -O3
(可能 -fwhole-program
)在编译和 link 时可以优化它。
如果内存 space 在您的项目中非常重要,请考虑编写 GCC plugin -or some GNU Binutils extension- to detect (and perhaps optimize) that case. Such a plugin could use some sqlite 数据库(在编译时)管理所有全局 const char
定义。
请注意,您想要的优化要求编译器确实检测到像 foo1 == foo2
这样的指针相等 never 测试(并且 const char*p1, *p2;
它永远不会碰巧 p1
是 foo1
,p2
是 foo2
并且指针相等性 p1 == p2
在程序运行时进行测试)。您可以使用 Frama-C 之类的工具来确保这一点。
更多的编译器正在转换
const char foo1[] = "SAME VALUE";
const char foo2[] = "VALUE";
相当于
const char foo1[] = "SAME VALUE";
const char foo2[] = foo1 + 5; //// since strlen("SAME ") is 5
我的建议是在你的构建过程中生成 C 代码,很好地记录它,并明确地共享这些数据。
另一种方法可能是使用预处理器(可能在 GNU m4 or GPP) or write your GCC plugin 之上定义一些 __builtin_my_shared_string
编译器内置。
您稍后评论:
Code is being generated by a tool/script and there are many such constants and files.
然后只需改进 tool/script 即可生成更好的代码。您的 C 生成工具可以使用一些 sqlite 数据库。
PS。 AFAIK,GCC 和 Clang 都可以进行这样的优化,但我不确定。由于它们是开源的,您可以改进它们。
PPS。您的问题可能是 Bismon static source code analyzer and related to both CHARIOT and DECODER 项目的用例。这似乎比你想象的要难。你可以联系这些项目的负责人。
这种优化称为string interning。
GCC 默认设置 -fmerge-constants
标志:
Attempt to merge identical constants (string constants and floating-point constants) across compilation units.
This option is the default for optimized compilation if the assembler and linker support it. Use -fno-merge-constants to inhibit this behavior.
Enabled at levels -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中分别定义如下(命题#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 [=13=]x8,%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 [=16=]x8,%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
:
Attempt to merge identical constants and identical variables.
This option implies -fmerge-constants. In addition to -fmerge-constants this considers e.g. even constant initialized arrays or initialized constant variables with integral or floating-point types. Languages like C or C++ require each variable, including multiple instances of the same variable in recursive calls, to have distinct locations, so using this option results in non-conforming behavior.
让我们用这个标志重建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 [=17=]x0,%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 定义:
f1:
const char foo1[] = "SAME_VALUE";
f2:
const char foo2[] = "SAME_VALUE";
想了解在最终的二进制文件中是否会对其进行优化以在内存中使用 common space。这是在 GCC
的上下文中阅读 C 标准,如 n1570. It requires that foo1 != foo2
when that test happens at runtime (after of course extern const char foo1[]; extern const char foo2[];
declarations). It could accept that a compiler optimizes if (foo1==foo2) abort();
(or assert(foo1 != foo2);
after some #include <assert.h>
, see assert(3)...) 到无操作。
Wanted to understand if in the final binary this will be optimized to take common space in memory.
这是非常特定于编译器的。
也许 GCC invoked as gcc -flto -O3
(可能 -fwhole-program
)在编译和 link 时可以优化它。
如果内存 space 在您的项目中非常重要,请考虑编写 GCC plugin -or some GNU Binutils extension- to detect (and perhaps optimize) that case. Such a plugin could use some sqlite 数据库(在编译时)管理所有全局 const char
定义。
请注意,您想要的优化要求编译器确实检测到像 foo1 == foo2
这样的指针相等 never 测试(并且 const char*p1, *p2;
它永远不会碰巧 p1
是 foo1
,p2
是 foo2
并且指针相等性 p1 == p2
在程序运行时进行测试)。您可以使用 Frama-C 之类的工具来确保这一点。
更多的编译器正在转换
const char foo1[] = "SAME VALUE";
const char foo2[] = "VALUE";
相当于
const char foo1[] = "SAME VALUE";
const char foo2[] = foo1 + 5; //// since strlen("SAME ") is 5
我的建议是在你的构建过程中生成 C 代码,很好地记录它,并明确地共享这些数据。
另一种方法可能是使用预处理器(可能在 GNU m4 or GPP) or write your GCC plugin 之上定义一些 __builtin_my_shared_string
编译器内置。
您稍后评论:
Code is being generated by a tool/script and there are many such constants and files.
然后只需改进 tool/script 即可生成更好的代码。您的 C 生成工具可以使用一些 sqlite 数据库。
PS。 AFAIK,GCC 和 Clang 都可以进行这样的优化,但我不确定。由于它们是开源的,您可以改进它们。
PPS。您的问题可能是 Bismon static source code analyzer and related to both CHARIOT and DECODER 项目的用例。这似乎比你想象的要难。你可以联系这些项目的负责人。
这种优化称为string interning。
GCC 默认设置 -fmerge-constants
标志:
Attempt to merge identical constants (string constants and floating-point constants) across compilation units.
This option is the default for optimized compilation if the assembler and linker support it. Use -fno-merge-constants to inhibit this behavior.
Enabled at levels -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中分别定义如下(命题#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 [=13=]x8,%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 [=16=]x8,%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
:
Attempt to merge identical constants and identical variables.
This option implies -fmerge-constants. In addition to -fmerge-constants this considers e.g. even constant initialized arrays or initialized constant variables with integral or floating-point types. Languages like C or C++ require each variable, including multiple instances of the same variable in recursive calls, to have distinct locations, so using this option results in non-conforming behavior.
让我们用这个标志重建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 [=17=]x0,%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'