我一直被告知,编译器足够智能,可以消除死代码。我编写的许多代码在编译时都包含大量已知信息,但代码必须以... 的形式编写。
我一直被告知编译器足够智能,可以消除死代码。我编写的大部分代码在编译时都有很多已知信息,但代码必须以最通用的形式编写。我不懂任何汇编语言,所以我无法检查生成的汇编语言。什么样的代码可以在最终的可执行文件中有效地消除?
举几个例子,但不限于
f(bool b){
if(b){
//some code
}else{
//some code
}
}
f(true);
//////////////////////////
template<bool b>
f(){
if(b){
//some code
}else{
//some code
}
}
f<true>();
///////////////////////////
如果 的定义 f
在其他目标代码中,而 被调用的 f(true)
在主代码中,会怎么样?链接时间优化能否有效消除死代码?有什么编码风格/编译器选项/技巧可以促进死代码的消除?
函数消除
GCC 可以执行的另一种类型的死代码消除是删除整个未使用的符号(函数或变量),可以通过以下方式实现:
-fdata-sections -ffunction-sections -Wl,--gc-sections
如上所述: 如何使用 GCC 和 ld 删除未使用的 C/C++ 符号?
默认情况下, 这些标志在各个 GCC -O 级别(-O1、-O2 等)中未启用 .
另如图所示: GCC LTO 是否执行跨文件死代码消除? 如果某个函数由于 LTO 而被内联,那么它不会计入将编译单元放置在最终输出中。
让我们尝试一些函数内 DCE
让我们来测试一下 GCC 能做什么或不能做什么,这很有趣。测试 LLM 模型很无聊。
主程序
int main(int argc, char **argv) {
if (0) {
argc += 0x33;
}
return argc;
}
编译和反汇编:
gcc -O0 main.c
gdb -batch -ex 'disassemble/rs main' a.out
输出:
0x0000000000001129 <+0>: f3 0f 1e fa endbr64
0x000000000000112d <+4>: 55 push %rbp
0x000000000000112e <+5>: 48 89 e5 mov %rsp,%rbp
0x0000000000001131 <+8>: 89 7d fc mov %edi,-0x4(%rbp)
0x0000000000001134 <+11>: 48 89 75 f0 mov %rsi,-0x10(%rbp)
0x0000000000001138 <+15>: 8b 45 fc mov -0x4(%rbp),%eax
0x000000000000113b <+18>: 5d pop %rbp
0x000000000000113c <+19>: c3 ret
是的,它消失了。
其他一些:
在 中没有被移除 -O3
,它不知道 argc
必须为正:
int main(int argc, char **argv) {
if (argc >= 0) {
argc += 0x33;
}
return argc;
}
0x44 被删除 -O3
但没有被删除 -O4
,因此它可以执行 if/else 算术:
int main(int argc, char **argv) {
if (argc >= 0) {
argc += 0x33;
} else {
if (argc >= 0) {
argc += 0x44;
}
}
return argc;
}
删除了 0x33, -O3
以便可以执行范围和 +
:
int main(int argc, char **argv) {
if (argc < 10000) {
if (argc + 1 > 10000) {
argc += 0x33;
}
}
return argc;
}
0x33 未被删除,因此无法推断 sqrt
:
#include <math.h>
int main(int argc, char **argv) {
if (argc < 10000) {
if (sqrt(argc) > 100) {
argc += 0x33;
}
}
return argc;
}