编译器是否会优化对常量变量的引用?

6

当涉及到C和C++语言时,编译器是否会优化对常量变量的引用,以便程序自动知道所引用的值,而不必窥视常量变量的内存位置?当涉及到数组时,是否取决于在编译时指向数组的索引值是否为常量?

例如,看一下这段代码:

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

编译器在编译时是否会将第二行“char erm = '3'”更改为其他形式?


11
根据编译器、版本、优化级别、代码复杂度等因素,可能会或可能不会发生。请检查编译器为目标平台生成的汇编代码。 - P.P
1
gcc6.2对于-O1或更高级别的优化效果符合预期:https://godbolt.org/g/yFLoYo - 0x5453
你可以通过获取反汇编器并查看反汇编的优化代码来相对容易地回答这些问题。例如,Codeblocks IDE -> 调试器窗口 -> 反汇编。你不必成为x86汇编语言专家,只需基于此获得大致的想法即可。 - Lundin
据我所知,通常情况下 const 只意味着对象的可观察状态不会改变。然而,一个 const 对象可能有 mutable 的私有字段,在这种情况下,不能使用副本来代替对对象引用的解引用。 - 463035818_is_not_a_number
3个回答

12

我个人认为,由于这两个变量都没有被使用,所以应该将发布的代码转换为“nothing”,因此可以将其删除。

但是,现代编译器(如gcc、clang、msvc等)应该能够用其常量值替换对备选项的引用[只要编译器可以合理地确定tesst的内容没有被更改——如果您将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 内部获取值。


8

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

使用最大优化,编译器可能只会用char erm = '3';替换你的整个代码。GCC -O3就是这样做的。

但这当然也取决于你如何使用该变量。编译器甚至可能不分配变量,而只在变量出现的操作中使用原始数字。


3
根据编译器版本、使用的优化选项和许多其他因素而定。如果您想确保 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,它的值由用户输入在执行时决定,然后调用tesst[i]。那么程序能够进行优化吗? - Måns Nilsson
1
constexpr表达式不能保证在编译时被求值。如果程序是良好形式的,那么在编译时求值constexpr表达式总是可能的,但编译器并不本质上有义务执行该求值。(然而,在使用这种表达式的上下文中可能会创建这样的义务。) - John Bollinger
@MånsNilsson 显然,编译器无法预测用户将要输入什么。但它可以预测您只使用了数组中的三分之一,并且可以摆脱未使用的部分。 - Lundin
@JohnBollinger constexpr变量不是必须在编译时计算吗? - NathanOliver
1
@NathanOliver,我可能错了,但我没有在标准中找到任何一般要求它的东西。constexpr表达式受到约束,使得可以在编译时评估它们,但据我所知,标准并不坚持编译时评估实际发生。 - John Bollinger
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接