【问题标题】:does g++ compiler treats constexpr functions as regular in a case their return value isnt being used?g++ 编译器是否将 constexpr 函数视为常规函数,以防它们的返回值未被使用?
【发布时间】:2020-04-19 14:00:21
【问题描述】:

我试图查看由 g++ 编译的 cpp constexpr 函数的编译代码。 我看到如果函数不返回任何内容,编译器会将其视为常规函数,但如果它返回一个值并将该值分配给 constexpr 变量,则只有在编译时才计算它。

代码示例:

constexpr int func(int x){
   return x!=0 ? 1: throw "Error";
}

int main(){
    func(2);
}

和编译器输出:

push    rbp
mov     rbp, rsp
mov     edi, 2
call    func(int)
mov     eax, 0
pop     rbp
ret

如您所见,它在运行时调用 func。相反,如果我将函数结果分配给 constexpr:

constexpr int func(int x){
    return x!=0 ? 1: throw "Error";
}

int main(){
   constexpr int x = func(2);
}

和编译器输出:

main:
    push    rbp
    mov     rbp, rsp
    mov     DWORD PTR [rbp-4], 1
    mov     eax, 0
    pop     rbp
    ret

谁能解释一下为什么编译器需要这个赋值来在编译时而不是在运行时评估函数?

【问题讨论】:

  • TIL 你可以将intvoid 表达式作为条件的第二和第三个操作数
  • @MM 仅当void 表达式是throw 表达式时,因此不需要在条件表达式的结果类型中考虑它,因为throw,如果执行,永远不能为它产生一个值。

标签: c++ g++ constexpr


【解决方案1】:

我编译了下面的代码来看看 gcc 11.2 是否优化了 constexpr 函数:

#include <iostream>
using namespace std;
    
constexpr double pi(){
 double a=0.;
 for(int i=1;i<100000;i+=4)a+=1./i-1./(i+2);
 return a*4;
}
    
int main(){
 constexpr int pi2=pi()*100;
 enum {pi3=pi2};
 cout<<pi3<<endl;
}

拆解如下:

# temp.cpp:10: int main(){
        call    __main   #
 # temp.cpp:11:  constexpr int pi2=pi()*100;
        movl    $314, -4(%rbp)   #, pi2
 # temp.cpp:13:  cout<<pi3<<endl;
        movl    $314, %edx       #,
        movq    .refptr._ZSt4cout(%rip), %rax    #, tmp85
        movq    %rax, %rcx       # tmp85,
        call    _ZNSolsEi        #
        movq    %rax, %rcx       #, _1
 # temp.cpp:13:  cout<<pi3<<endl;
        movq    .refptr._ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_(%rip), %rax   #, tmp86
        movq    %rax, %rdx       # tmp86,
        call    _ZNSolsEPFRSoS_E         #
 # temp.cpp:14: }
        movl    $0, %eax         #, _7
        addq    $48, %rsp        #,
        popq    %rbp     #
        ret

因此,gcc 可以在编译时评估 constexpr 函数。

如果需要更长的计算,gcc 标志-fconstexpr-* 应该增加。但我观察到编译时间计算比(-O3 flag)执行时间计算需要更多时间。

如果需要更长的计算,我建议,

  • 编写代码以生成代码(如计算上面示例中的pi),
  • 编译并运行该代码,
  • 将生成的代码与其余代码一起编译。

makefile 来执行上述步骤会很有帮助。

【讨论】:

    【解决方案2】:

    允许编译器决定是否在编译时或运行时评估 constexpr 函数。只有在需要编译时常量表达式的上下文中使用函数时(例如使用结果初始化constexpr 变量),编译器必须在编译时评估函数。

    在您的第一个示例中,情况并非如此,因为您可能在调试模式下编译,该函数在运行时调用,就像其他所有函数一样。

    来自cppreference 的引用(由我突出显示):

    constexpr 说明符声明可以在编译时评估函数或变量的值。然后可以在仅允许编译时常量表达式的情况下使用此类变量和函数(前提是给出了适当的函数参数)。

    如果你编译你的第一个例子,例如-O3 你会看到函数调用被优化掉了。

    【讨论】:

    • 奇怪的是,如果我将它与 O3 一起编译,那么编译器似乎只是内联了我的函数,并没有在编译时真正评估它。
    • 例如,如果我将值 0 传递给函数(我希望它会在这样的 constexpr 函数中导致编译时错误),它会将 throw 内联到 main 并在运行时抛出异常
    • 如果 constexpr 函数中的代码路径抛出异常,则该函数不能在需要编译时常量表达式的上下文中使用。见这里:godbolt.org/z/dRXo4t
    猜你喜欢
    • 1970-01-01
    • 2011-12-08
    • 2018-03-11
    • 2013-09-09
    • 2016-12-15
    • 2019-12-04
    • 1970-01-01
    • 1970-01-01
    • 2019-05-27
    相关资源
    最近更新 更多