constexpr 常量表达式是 C++ 11 语言标准里面提出的 “编译器语法功能”,其作用顾名思义为:常量表达式,尽量在编译阶段计算其结果。
例如:
#include <stdio.h>
int add(int x, int y) {
return x + y;
}
void test() {
int n = add(1, 2);
printf("%d\n", n);
}
汇编: WINE-x86_32 + MSVC 2017(VC++ 2017)
$SG5547 DB '%d', 0aH, 00H
unsigned __int64 `__local_stdio_printf_options'::`2'::_OptionsStorage DQ 01H DUP (?) ; `__local_stdio_printf_options'::`2'::_OptionsStorage
_x$ = 8 ; size = 4
_y$ = 12 ; size = 4
int add(int,int) PROC ; add, COMDAT
push ebp
mov ebp, esp
mov eax, DWORD PTR _x$[ebp]
add eax, DWORD PTR _y$[ebp]
pop ebp
ret 0
int add(int,int) ENDP ; add
_n$ = -4 ; size = 4
void test(void) PROC ; test
push ebp
mov ebp, esp
push ecx
push 2
push 1
call int add(int,int) ; add
add esp, 8
mov DWORD PTR _n$[ebp], eax
mov eax, DWORD PTR _n$[ebp]
push eax
push OFFSET $SG5547
call _printf
add esp, 8
mov esp, ebp
pop ebp
ret 0
void test(void) ENDP ; test
那么我们尝试为 add 函数增加 constexpr 关键字后再来观察其编译结果:
#include <stdio.h>
constexpr int add(int x, int y) {
return x + y;
}
void test() {
int n = add(1, 2);
printf("%d\n", n);
}
注意需要打开 C/C++ 编译器优化: /Ox -O3
#include <stdio.h>
constexpr int add(int x, int y) {
return x + y;
}
void test() {
int n = add(1, 2);
printf("%d\n", n);
}
汇编: WINE-x86_32 + MSVC 2017(VC++ 2017)
$SG5547 DB '%d', 0aH, 00H
unsigned __int64 `__local_stdio_printf_options'::`2'::_OptionsStorage DQ 01H DUP (?) ; `__local_stdio_printf_options'::`2'::_OptionsStorage
_x$ = 8 ; size = 4
_y$ = 12 ; size = 4
int add(int,int) PROC ; add, COMDAT
mov eax, DWORD PTR _x$[esp-4]
add eax, DWORD PTR _y$[esp-4]
ret 0
int add(int,int) ENDP ; add
void test(void) PROC ; test
push 3
push OFFSET $SG5547
call _printf
add esp, 8
ret 0
void test(void) ENDP ; test
人们从上述两个编译汇编结果中可以得出,C/C++ 编译器已经在编译期间把 add(1, 2) 的结果直接计算出来,并适用静态常量数值(3:结果)来代替原有代码需要调用 add 函数的情况。
如上述代码所示:
push 3,为调用 _printf 函数参数入栈,push offset $SG5547 为从DB数据中 fmt 字符串参数RVA地址,平衡函数调用参数栈大小:8字节(__cdecl "C" 函数调用协议)
但需要注意:
如果在不打开 C/C++ 编译器优化的情况下,上面两个 C/C++ 代码编译后的结果其实是一致性的,即不会产生任何的静态表达式常量优化。
我会很好奇:
人们进一步尝试,add(1,2) 函数传递一个变量,那么 C/C++ 编译器优化后,最终编译输出的原生代码结果会是怎么样的?
#include <stdio.h>
constexpr int add(int x, int y) {
return x + y;
}
void test(int left) {
int n = add(left, 2);
printf("%d\n", n);
}
汇编: WINE-x86_32 + MSVC 2017(VC++ 2017)
$SG5548 DB '%d', 0aH, 00H
unsigned __int64 `__local_stdio_printf_options'::`2'::_OptionsStorage DQ 01H DUP (?) ; `__local_stdio_printf_options'::`2'::_OptionsStorage
_x$ = 8 ; size = 4
_y$ = 12 ; size = 4
int add(int,int) PROC ; add, COMDAT
mov eax, DWORD PTR _x$[esp-4]
add eax, DWORD PTR _y$[esp-4]
ret 0
int add(int,int) ENDP ; add
_left$ = 8 ; size = 4
void test(int) PROC ; test
mov eax, DWORD PTR _left$[esp-4]
add eax, 2
push eax
push OFFSET $SG5548
call _printf
add esp, 8
ret 0
void test(int) ENDP ; test
如上述汇编代码所示,当我们进行 C/C++ 编译器优化时,类似简单的 add 简单的运算表达式,可以被优化为 inline 的形式,但它并非明确的静态常量结果,而是需要进行动态计算。
add(left, 2),参数二为明确常量在被 C/C++ 编译器优化时,被明确为静态常量,其加值计算过程被内联(inline),所以可以清楚一个明确的点。
即:
constexpr 关键字声明的函数不需要 C/C++ 开发人员明确的增加 “inline” 关键字作为声明,最终 C/C++ 语言编译器,编译的结果是根据预期为 inline 来处理的。
人们在尝试更进一步,add(1,2) 函数传递全部变量时,那么 C/C++ 编译器优化后,最终编译输出的原生代码结果会是怎么样的?
#include <stdio.h>
constexpr int add(int x, int y) {
return x + y;
}
void test(int x, int y) {
int n = add(x, y);
printf("%d\n", n);
}
汇编: WINE-x86_32 + MSVC 2017(VC++ 2017)
$SG5549 DB '%d', 0aH, 00H
unsigned __int64 `__local_stdio_printf_options'::`2'::_OptionsStorage DQ 01H DUP (?) ; `__local_stdio_printf_options'::`2'::_OptionsStorage
_x$ = 8 ; size = 4
_y$ = 12 ; size = 4
int add(int,int) PROC ; add, COMDAT
mov eax, DWORD PTR _x$[esp-4]
add eax, DWORD PTR _y$[esp-4]
ret 0
int add(int,int) ENDP ; add
_x$ = 8 ; size = 4
_y$ = 12 ; size = 4
void test(int,int) PROC ; test
mov eax, DWORD PTR _x$[esp-4]
add DWORD PTR _y$[esp-4], eax
mov DWORD PTR _x$[esp-4], OFFSET $SG5549
jmp _printf
void test(int,int) ENDP ; test
上述汇编为 C/C++ 编译器优化以后,其输出汇编代码的结果与上一个测试结果类似,但鉴于没有任何静态常量参数,所以整个计算过程被 inline(内联)后,其传入参数之间的运算为,动态载入计算堆栈其计算结果被推入计算堆栈后调用 _printf 函数大约输出结果到控制台屏幕上。
constexpr 关键字,C++ 编译器语言边界限制
第一条:
#include <stdio.h>
constexpr int add(int x, int y) {
return x + y;
}
constexpr void test(int x, int y) {
int n = add(x, y);
printf("%d\n", n);
}
如上述代码似乎没有任何问题,但 constexpr void test(int x, int y) 函数无法被编译,因为它无法被 C/C++ 编译器理解为一条有效的 “常量表达式”。
原因为:
printf("%d\n", n),命令代码,删除它即可正确编译代码,从此处可以得出 constexpr 常量表达式函数,可以是递归调用执行的。
第二条:
struct people {
int age;
constexpr people() {
}
}
上述代码结构体 “构造函数(constructor)” 上声明了 constexpr,似乎看上去没有问题,但这是错误的,构造函数是可变不确定的,当我们要构造一个结构及类时,构造函数内部实现无法确定且构造函数会改变结构本身的数据,并且从基础概念上构造函数与常量表达式两个不同的概念及产物,不可混为一谈,注意上述代码无法被 C/C++ 编译器编译通过。
第三条:
class people {
int age;
public:
constexpr ~people() {
}
}
上述代码 “类析构函数” 上声明了 constexpr,似乎看上去没有问题,但问题与上述 “第二条” 存在的问题是相同的,析构函数是可变不确定的。