当 constexpr 函数出错时会发生什么?
What happens when there's an error in constexpr function?
我了解到 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
函数是在编译时求值的,为什么我在编译和 link 时看不到错误?
进一步,我发现assembled 这段代码,结果发现这个函数没有被评估,而是作为一个普通函数被调用:
(gdb) x/10i $pc
=> 0x80007ca <main()>: sub [=11=]x8,%rsp
0x80007ce <main()+4>: mov [=11=]x7a11f,%edi
0x80007d3 <main()+9>: callq 0x8000823 <fac(int)>
0x80007d8 <main()+14>: imul [=11=]x7a120,%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 [=11=]x0,%eax
0x80007ef <main()+37>: add [=11=]x8,%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;
}
assemble代码变成:
(gdb) x/10i $pc
=> 0x80007ca <main()>: sub [=13=]x8,%rsp
0x80007ce <main()+4>: mov [=13=]x78,%esi
0x80007d3 <main()+9>: lea 0x200846(%rip),%rdi # 0x8201020 <_ZSt4cout@@GLIBCXX_3.4>
0x80007da <main()+16>: callq 0x80006a0 <_ZNSolsEi@plt>
0x80007df <main()+21>: mov [=13=]x0,%eax
0x80007e4 <main()+26>: add [=13=]x8,%rsp
0x80007e8 <main()+30>: retq
fac
函数在编译时求值。
有人能解释一下吗?
编译命令:
g++ -Wall test.cpp -g -O1 -o test
以及 g++ 版本 7.4.0
、gdb 版本 8.1.0
I learnt that constexpr functions are evaluated at compile time
不,constexpr
可以在编译时求值,也可以在运行时求值。
进一步阅读:
- Difference between `constexpr` and `const`
- https://en.cppreference.com/w/cpp/language/constexpr
Apparently this code would throw an error
不,没有抛出任何错误。对于大输入,结果会溢出,这是未定义的行为。这并不意味着将抛出或显示 error。这意味着任何事情都可能发生。当我说什么的时候,我的意思是什么。该程序可能会崩溃、挂起、出现奇怪的结果、显示奇怪的字符,或者任何字面意思。
进一步阅读:
- https://en.cppreference.com/w/cpp/language/ub
- https://en.wikipedia.org/wiki/Undefined_behavior
- http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html
而且,正如 nathanoliver
所指出的
when invoked in a constant expression, a constexpr function must check and error out on UB http://coliru.stacked-crooked.com/a/43ccf2039dc511d5
也就是说编译时不能有任何UB。在运行时是UB,在编译时是硬错误。
constexpr
表示它可以在编译时求值,而不是说它会在编译时求值。如果您在需要编译时常量(例如数组的大小)的地方使用它,编译器将被迫在编译时进行评估。
另一方面,对于小值,例如 g++ 足够智能以计算结果编译时间(即使没有 constexpr
)。
例如:
int fact(int n) {
return n < 2 ? 1 : n*fact(n-1);
}
int bar() {
return fact(5);
}
g++ -O3
为 bar
生成的代码是:
bar():
mov eax, 120
ret
请注意,溢出调用堆栈(例如无限递归或过度递归)甚至溢出有符号整数算法在 C++ 中是未定义的行为,任何事情都可能发生。这并不意味着你会得到一个很好的 "error" 甚至是一个段错误......但任何事情都可能发生(包括,不幸的是,没有明显的)。基本上这意味着编译器的作者可以忽略处理这些情况,因为你不应该犯这种错误。
我了解到 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
函数是在编译时求值的,为什么我在编译和 link 时看不到错误?
进一步,我发现assembled 这段代码,结果发现这个函数没有被评估,而是作为一个普通函数被调用:
(gdb) x/10i $pc
=> 0x80007ca <main()>: sub [=11=]x8,%rsp
0x80007ce <main()+4>: mov [=11=]x7a11f,%edi
0x80007d3 <main()+9>: callq 0x8000823 <fac(int)>
0x80007d8 <main()+14>: imul [=11=]x7a120,%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 [=11=]x0,%eax
0x80007ef <main()+37>: add [=11=]x8,%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;
}
assemble代码变成:
(gdb) x/10i $pc
=> 0x80007ca <main()>: sub [=13=]x8,%rsp
0x80007ce <main()+4>: mov [=13=]x78,%esi
0x80007d3 <main()+9>: lea 0x200846(%rip),%rdi # 0x8201020 <_ZSt4cout@@GLIBCXX_3.4>
0x80007da <main()+16>: callq 0x80006a0 <_ZNSolsEi@plt>
0x80007df <main()+21>: mov [=13=]x0,%eax
0x80007e4 <main()+26>: add [=13=]x8,%rsp
0x80007e8 <main()+30>: retq
fac
函数在编译时求值。
有人能解释一下吗?
编译命令:
g++ -Wall test.cpp -g -O1 -o test
以及 g++ 版本 7.4.0
、gdb 版本 8.1.0
I learnt that constexpr functions are evaluated at compile time
不,constexpr
可以在编译时求值,也可以在运行时求值。
进一步阅读:
- Difference between `constexpr` and `const`
- https://en.cppreference.com/w/cpp/language/constexpr
Apparently this code would throw an error
不,没有抛出任何错误。对于大输入,结果会溢出,这是未定义的行为。这并不意味着将抛出或显示 error。这意味着任何事情都可能发生。当我说什么的时候,我的意思是什么。该程序可能会崩溃、挂起、出现奇怪的结果、显示奇怪的字符,或者任何字面意思。
进一步阅读:
- https://en.cppreference.com/w/cpp/language/ub
- https://en.wikipedia.org/wiki/Undefined_behavior
- http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html
而且,正如 nathanoliver
所指出的when invoked in a constant expression, a constexpr function must check and error out on UB http://coliru.stacked-crooked.com/a/43ccf2039dc511d5
也就是说编译时不能有任何UB。在运行时是UB,在编译时是硬错误。
constexpr
表示它可以在编译时求值,而不是说它会在编译时求值。如果您在需要编译时常量(例如数组的大小)的地方使用它,编译器将被迫在编译时进行评估。
另一方面,对于小值,例如 g++ 足够智能以计算结果编译时间(即使没有 constexpr
)。
例如:
int fact(int n) {
return n < 2 ? 1 : n*fact(n-1);
}
int bar() {
return fact(5);
}
g++ -O3
为 bar
生成的代码是:
bar():
mov eax, 120
ret
请注意,溢出调用堆栈(例如无限递归或过度递归)甚至溢出有符号整数算法在 C++ 中是未定义的行为,任何事情都可能发生。这并不意味着你会得到一个很好的 "error" 甚至是一个段错误......但任何事情都可能发生(包括,不幸的是,没有明显的)。基本上这意味着编译器的作者可以忽略处理这些情况,因为你不应该犯这种错误。