8wDlpd.png
8wDFp9.png
8wDEOx.png
8wDMfH.png
8wDKte.png

GCC 可以从最终输出中消除哪种死代码?

Thebest 2月前

55 0

我一直被告知,编译器足够智能,可以消除死代码。我编写的许多代码在编译时都包含大量已知信息,但代码必须以... 的形式编写。

我一直被告知编译器足够智能,可以消除死代码。我编写的大部分代码在编译时都有很多已知信息,但代码必须以最通用的形式编写。我不懂任何汇编语言,所以我无法检查生成的汇编语言。什么样的代码可以在最终的可执行文件中有效地消除?

举几个例子,但不限于

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) 在主代码中,会怎么样?链接时间优化能否有效消除死代码?有什么编码风格/编译器选项/技巧可以促进死代码的消除?

帖子版权声明 1、本帖标题:GCC 可以从最终输出中消除哪种死代码?
    本站网址:http://xjnalaquan.com/
2、本网站的资源部分来源于网络,如有侵权,请联系站长进行删除处理。
3、会员发帖仅代表会员个人观点,并不代表本站赞同其观点和对其真实性负责。
4、本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
5、站长邮箱:yeweds@126.com 除非注明,本帖由Thebest在本站《visual-studio》版块原创发布, 转载请注明出处!
最新回复 (0)
  • 当我在这样的 if 表达式中使用模板参数常量时,dce(死代码消除)编译器(Linux 上的 GCC 4.8.1)标志没有帮助,O2、O3 优化也没有帮助。我不得不使用模板特化包装器:

    template<bool b>
    f();
    
    template<>
    f<true>(){
      //some code on true condition
    }
    
    template<>
    f<false>(){
      //some code on false condition
    }
    

    也可以使用宏来避免编译未使用的代码分支,但这取决于编译器(它是在代码中还是在预编译阶段处理宏):

    template<bool b>
    f(){
     #if b
      //some code
     #elif
      //some code
     #endif  // b
    }
    
  • 函数消除

    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;
    }
    
  • 您引用的介绍与当前 GCC 手册页中关于 -f*-sections 选项的内容不一致——“仅当这样做有显著好处时才使用这些选项。当您指定这些选项时,汇编器和链接器会创建更大的对象和可执行文件,并且速度也会变慢。它们会阻止编译器和汇编器使用翻译单元内的相对位置进行优化...”

  • 仅供参考,如果您的代码或库需要魔法部分,那么链接器脚本应该将这些部分标记为 KEEP。这种事情在嵌入式开发中经常发生(这通常需要链接器脚本来定义内存布局),因为诸如异常向量表之类的东西永远不会被主程序调用或引用,但需要存在于平台中。顺便说一句,如果您在没有 -ffunction-sections、-fdata-sections 和 --gc-sections` 的情况下构建嵌入式代码,那么您只会伤害自己。

  • @*:RTL = 寄存器传输语言是一种受 Lisp 启发的低级中间语言(GCC 使用的三种中间语言之一),与另一种称为 GIMPLE 的语言一起用于优化目的。

  • 通常,如果您使用 the -O flag ,则会打开以下标志:

          -fauto-inc-dec 
          -fcompare-elim 
          -fcprop-registers 
          -fdce  
          [...]
    

    -fdce 代表“死代码消除”。我建议您在编译二进制文件时,先启用或禁用此选项(即明确关闭此选项),以确保二进制文件是否按您希望的方式进行了优化。

    了解 编译器的 不同过程

    • SSA 积极死代码消除。由“-fssa-dce”选项启用。此过程执行消除被认为不必要的代码的操作,因为这些代码对程序没有外部可见的影响。它以线性时间运行。

    至于如何帮助链接器消除死代码,请阅读 本演示文稿 。两个主要要点是:

    使用 -ffunction-sections -fdata-sections 编译您的模块 – 它没有任何缺点!

    • 这包括静态库,而不仅仅是二进制文件——使您的库的用户能够受益于更有效的死代码删除。
    • 将您的二进制文件与--gc-sections链接,除非您必须链接到使用魔术部分的令人讨厌的第三方静态库。

    您可能还想看看 这个 GCC 错误 (看看可能会错过哪些优化机会以及原因)。

  • 查看 GCC 文档。那里是否列举了所有可能的死代码情况?如果没有,您不太可能在任何地方找到它们。

  • @JesseGood 这很有帮助!谢谢。但我不需要知道哪些代码是死的,我只想确保它们不会被编译到最终的可执行文件中。事实上,当我编写调用者代码时,我故意知道哪部分代码是死的。

返回
作者最近主题: