前言
最开始看const和constexpr的区别,知道const用来限定只读语义,constexpr用来定义常量语义。只不过“只读”和“常量”的含义,略微有点抽象。其实前者就是指运行期的“不可变性”,后者就是用来实现编译期计算。那么就计算机底层而言,二者到底会有什么不同呢?下面就从汇编指令来直观体会一下什么是constexpr的“编译期计算”,也顺便理解理解“运行时”和“编译时”、“动态”和“静态”这些名词。
constexpr 常量
const和constexpr都可以定义编译期常量。
- 定义变量a
void test(){
int a = 1;
int b = a + 1;
}
对应汇编指令为,
test():
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], 1 # 定义a = 1
mov eax, DWORD PTR [rbp-4] # 把a存入寄存器eax
add eax, 1 # 执行eax += 1
mov DWORD PTR [rbp-8], eax # 把eax存入b
nop
pop rbp
ret
以上包含了计算b的过程,需要从eax寄存器读取b的值。
- 定义constexpr/const常量a
void test(){
constexpr int a = 1;
int b = a + 1;
}
对应汇编指令为:
test():
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], 1 # 定义a = 1
mov DWORD PTR [rbp-8], 2 # 定义b = 2
nop
pop rbp
ret
与未使用constexpr相比,少了两条指令,采用的是立即数寻址的方式,直接将2存入了b中。
constexpr 函数
- 定义不带constexpr的函数
int square(int num) {
return num * num;
}
int main(){
int b = 1 + square(1);
return 0;
}
对应汇编指令为:
square(int):
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], edi
mov eax, DWORD PTR [rbp-4]
imul eax, eax
pop rbp
ret
main:
push rbp
mov rbp, rsp
sub rsp, 16
mov edi, 1 # 把参数1存入寄存器edi
call square(int) # 调用square函数
add eax, 1
mov DWORD PTR [rbp-4], eax
mov eax, 0
leave
ret
在运行时会调用square函数进行计算。
- 定义constexpr函数
constexpr int square(int num) {
return num * num;
}
int main(){
int b = 1 + square(1);
return 0;
}
对应汇编指令为:
main:
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], 2 # 把2存入b
mov eax, 0
pop rbp
ret
当直接传入常数2到 square
函数,与未使用constexpr相比,少了多条汇编指令,没有了函数调用的指令,直接将2存入了b中。
总结
以上从底层汇编指令的角度体会了一下什么是constexpr的编译期计算,总之,const用于限定变量运行期的“不可变性”;constexpr用于定义常量、常量表达式或常函数,实现编译期计算。