constexpr函数出错时会发生什么?

时间:2019-08-19 12:11:26

标签: c++ c++11 constexpr

我了解到constexpr函数是在编译时评估的。但是看这个例子:

constexpr int fac(int n)
{
    return (n>1) ? n*fac(n-1) : 1;
}


int main()
{
    const int a = 500000;
    cout << fac(a);
    return 0;
}

显然,此代码将引发错误,但是由于constexpr函数是在编译时求值的,为什么在编译和链接时看不到错误?

然后,我反汇编了这段代码,结果发现该函数没有被评估,而是被称为普通函数:

(gdb) x/10i $pc
=> 0x80007ca <main()>:  sub    $0x8,%rsp
   0x80007ce <main()+4>:        mov    $0x7a11f,%edi
   0x80007d3 <main()+9>:        callq  0x8000823 <fac(int)>
   0x80007d8 <main()+14>:       imul   $0x7a120,%eax,%esi
   0x80007de <main()+20>:       lea    0x20083b(%rip),%rdi        # 0x8201020 <_ZSt4cout@@GLIBCXX_3.4>
   0x80007e5 <main()+27>:       callq  0x80006a0 <_ZNSolsEi@plt>
   0x80007ea <main()+32>:       mov    $0x0,%eax
   0x80007ef <main()+37>:       add    $0x8,%rsp
   0x80007f3 <main()+41>:       retq

但是,如果我打来fac(5)

constexpr int fac(int n)
{
    return (n>1) ? n*fac(n-1) : 1;
}


int main()
{
    const int a = 5;
    cout << fac(a);
    return 0;
}

汇编代码变成:

(gdb) x/10i $pc
=> 0x80007ca <main()>:  sub    $0x8,%rsp
   0x80007ce <main()+4>:        mov    $0x78,%esi
   0x80007d3 <main()+9>:        lea    0x200846(%rip),%rdi        # 0x8201020 <_ZSt4cout@@GLIBCXX_3.4>
   0x80007da <main()+16>:       callq  0x80006a0 <_ZNSolsEi@plt>
   0x80007df <main()+21>:       mov    $0x0,%eax
   0x80007e4 <main()+26>:       add    $0x8,%rsp
   0x80007e8 <main()+30>:       retq

fac函数在编译时进行评估。

任何人都可以解释吗?

编译命令:

g++ -Wall test.cpp -g -O1 -o test

对于g ++版本7.4.0,gdb版本8.1.0

2 个答案:

答案 0 :(得分:4)

  

我了解到constexpr函数是在编译时评估的

否,constexpr 可以在编译时进行评估,也可以在运行时进行评估。

进一步阅读:

  

显然,这段代码会引发错误

否,没有引发任何错误。对于较大的输入,结果将溢出,这是未定义的行为。这并不意味着会抛出或显示错误。这意味着任何事情都可能发生。当我说什么时,我的意思是什么。该程序可能会崩溃,挂起,看起来会产生奇怪的结果,显示奇怪的字符或其他任何内容。

进一步阅读:

而且,如nathanoliver

所指出
  

在常量表达式中调用时,constexpr函数必须在UB http://coliru.stacked-crooked.com/a/43ccf2039dc511d5上检错并出错误

换句话说,在编译时不能有任何UB。在运行时将是UB,在编译时将是一个硬错误。

答案 1 :(得分:3)

constexpr表示可以在编译时进行评估,而不是在编译时进行评估。如果在预期编译时间常数(例如数组大小)的地方使用编译器,则将被迫执行评估编译时。

另一方面,对于较小的值,例如,g ++足够聪明,可以计算结果编译时间(即使没有constexpr)。

例如:

int fact(int n) {
    return n < 2 ? 1 : n*fact(n-1);
}

int bar() {
    return fact(5);
}

g++ -O3bar生成的代码为:

bar():
    mov     eax, 120
    ret

请注意,在C ++中,调用栈溢出(例如,无限或过多的递归)甚至是有符号整数算法都溢出,这是可能发生的,任何事情都可能发生。这并不意味着您会得到一个很好的“错误”甚至是段错误……但是任何事情都可能发生(不幸的是,没有任何迹象表明)。基本上,这意味着编译器的作者可以忽略处理这些情况,因为您不应该犯此类错误。