【问题标题】:Does the compiler optimize references to constant variables?编译器是否优化对常量变量的引用?
【发布时间】:2017-05-04 14:50:39
【问题描述】:

对于 C 和 C++ 语言,编译器是否优化了对常量变量的引用,以便程序自动知道引用了哪些值,而不必查看常量变量的内存位置?说到数组,是否取决于数组中指向的索引值在编译时是否为常量?

例如,看看这段代码:

int main(void) {
    1:  char tesst[3] = {'1', '3', '7'};
    2:  char erm = tesst[1];
}

编译器是否在编译时将第 2 行“更改”为“char erm = '3'”?

【问题讨论】:

  • 它可能会也可能不会 - 取决于编译器、其版本、优化级别、所涉及代码的复杂性等。检查您的编译器为您的目标平台生成的程序集。
  • gcc6.2 会根据需要优化 -O1 或更高版本:godbolt.org/g/yFLoYo
  • 您可以通过获取反汇编程序并查看经过反汇编的优化代码自己轻松回答这些问题。例如 Codeblocks IDE -> 调试器窗口 -> 反汇编。您不必成为 x86 汇编器方面的专家,也可以基于此获得粗略的想法。
  • afaik,一般const 只表示对象的可观察状态不能改变。但是,const 对象可能具有 mutable 私有字段,在这种情况下,不能使用副本来代替对对象的引用

标签: c++ c performance optimization memory-management


【解决方案1】:

取决于编译器版本、使用的优化选项和许多其他因素。如果你想确保 const 变量被优化并且如果它们是编译时常量,你可以在 c++ 中使用类似 constexpr 的东西。与普通的 const 变量不同,它保证在编译时被评估。

编辑: constexpr 可以在编译时或运行时进行评估。为了保证编译时评估,我们必须要么在需要常量表达式的地方使用它(例如,作为数组绑定或作为 case 标签),要么使用它来初始化 constexpr。所以在这种情况下

constexpr char tesst[3] = {'1','3','7'};
constexpr char erm = tesst[1];

会导致编译时评估。在https://isocpp.org/blog/2013/01/when-does-a-constexpr-function-get-evaluated-at-compile-time-stackoverflow阅读很好

【讨论】:

  • 如果我有一个整数变量 i,其值在执行时由用户输入决定,然后调用 testst[i]。程序也可以为此优化吗?
  • constexpr 表达式不能保证在编译时被计算。如果程序格式正确,那么总是可能在编译时评估constexpr,但编译器本身并没有义务执行该评估。 (然而,使用这种表达的上下文可能会产生这种义务。)
  • @MånsNilsson 显然,编译器无法预测用户将键入什么内容。但它可以预测你只使用了数组的 3 个字节中的 1 个,并摆脱了未使用的字节。
  • @JohnBollinger constexpr 变量不是必须在编译时计算吗?
  • @NathanOliver,我对此持开放态度,但我没有在标准中找到任何需要它的东西。 constexpr 表达式受制于可以在编译时评估它们的约束,但据我所知,该标准并不坚持编译时评估实际上发生。
【解决方案2】:

这主要取决于优化级别和您使用的编译器。

通过最大程度的优化,编译器确实可能会将您的整个代码替换为char erm = '3';。 GCC -O3 无论如何都会这样做。

但是当然这取决于你对那个变量做了什么。编译器甚至可能不分配变量,而只是在变量出现的操作中使用原始数字。

【讨论】:

    【解决方案3】:

    我个人希望发布的代码变成“无”,因为这两个变量都没有实际使用,因此可以删除。

    但是,是的,现代编译器(gcc、clang、msvc 等)应该能够用它的常量值替换对替代项的引用 [只要编译器可以合理地确定 tesst 的内容不是' t 被改变 - 如果你将 tesst 传递给一个函数,即使它作为 const 引用,并且编译器实际上并不知道函数没有改变它,它会假设它确实并加载值] .

    使用clang -O1 opts.c -S编译:

    #include <stdio.h>
    
    int main()
    {
        char tesst[3] = {'1', '3', '7'};
        char erm = tesst[1];
    
        printf("%d\n", erm);
    }
    

    产生:

    ...
    
    main:
        pushq   %rax
    .Ltmp0:
        movl    $.L.str, %edi
        movl    $51, %esi
        xorl    %eax, %eax
        callq   printf
        xorl    %eax, %eax
        popq    %rcx
        retq
    
     ...
    

    所以,和printf("%d\n", '3');一样。

    [我使用的是 C 而不是 C++,因为如果我使用 cout 将需要大约 50 行汇编程序,因为所有内容都会被内联]

    我希望 gcc 和 msvc 进行类似的优化(测试 gcc -O1 -S 并给出完全相同的代码,除了一些符号名称略有不同)

    并且为了说明“如果你调用一个函数它可能不会这样做”:

    #include <stdio.h>
    
    extern void blah(const char* x);
    
    int main()
    {
        char tesst[3] = {'1', '3', '7'};
        blah(tesst);
        char erm = tesst[1];
    
        printf("%d\n", erm);
    }
    
    
    main:                                   # @main
        pushq   %rax
        movb    $55, 6(%rsp)
        movw    $13105, 4(%rsp)         # imm = 0x3331
        leaq    4(%rsp), %rdi
        callq   blah
        movsbl  5(%rsp), %esi
        movl    $.L.str, %edi
        xorl    %eax, %eax
        callq   printf
        xorl    %eax, %eax
        popq    %rcx
        retq
    

    现在,它从tesst 内部获取值。

    【讨论】:

      猜你喜欢
      • 2012-09-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-12-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多