constexpr 关键字的意义(入门)


author: hjjdebug

date: 2025年 05月 15日 星期四 16:03:33 CST

description: constexpr 关键字的意义(入门)


constexpr 是c++11 引入的一个关键字, 代表了一种属性.

文章目录

    • [1. constexpr 修饰的变量, 在编译期间就可以得到其数值.](#1. constexpr 修饰的变量, 在编译期间就可以得到其数值.)
    • [2. constexpr 修饰的函数, 可以在编译期间被调用.](#2. constexpr 修饰的函数, 可以在编译期间被调用.)
    • [3. 研究代码](#3. 研究代码)
    • [4. constexpr 修饰的变量, 可以调用constexpr 修饰的函数来赋值](#4. constexpr 修饰的变量, 可以调用constexpr 修饰的函数来赋值)
    • [5. constexpr 修饰的函数怎样调试呢?](#5. constexpr 修饰的函数怎样调试呢?)
    • [6. constexpr 修饰的函数, 在release版本下是怎样工作的?](#6. constexpr 修饰的函数, 在release版本下是怎样工作的?)
    • [7. constexpr 还可以修饰构造函数,成员函数.](#7. constexpr 还可以修饰构造函数,成员函数.)
      • [7.1 在常量表达式函数中你不能调用printf 函数](#7.1 在常量表达式函数中你不能调用printf 函数)
      • [7.2 在运行函数中调用constexpr 对象](#7.2 在运行函数中调用constexpr 对象)
    • [8 小结:](#8 小结:)

1. constexpr 修饰的变量, 在编译期间就可以得到其数值.

例如: constexpr int arr_ce[5]={1,2,3,4,5};

这里编译器gcc 会给arr_ce 变量加上 constexpr 属性.

这里你会说,我不加constexpr 不是一样声明变量并赋初值啊.

int arr_ce[5]={1,2,3,4,5};

是的, 这样也声明并初始化了变量arr_ce[5], 但它们没有constexpr属性,

那constexpr 属性有什么用? 且看后面慢慢道来.

2. constexpr 修饰的函数, 可以在编译期间被调用.

例如: 费伯纳兹函数F, 我们加上constexpr 修饰词.

constexpr int F(int n) {

return n == 0 ? 0 :

(n == 1 ? 1: F(n-1)+F(n-2));

}

有了这个属性和没有这个属性有什么差别呢?

答案来了,有这个属性的构成一个群! 构成一个在编译时就可以执行的群.

下面举例说明.

3. 研究代码

cpp 复制代码
$ cat main.cpp
#include <stdio.h>
//constexpr 修饰的函数,可以在编译期计算.
constexpr int F(int n) {  
    return n == 0 ? 0 : 
		(n == 1 ? 1: F(n-1)+F(n-2));
}
//constexpr 修饰的变量,可以调用constexpr 修饰的函数来赋值
//直接得到常量数据
constexpr int arr_ce[5]={
	F(0),F(1),F(2),F(3),F(4)
};

int arr_n[5];
struct Point {
//在常量表达式函数中你不能调用printf 函数
    constexpr Point(int x, int y) : x(x), y(y) {/*printf("Point construct\n");*/}
    int x, y;
};

constexpr Point p1(1, 2);  // ✅ 合法:在编译期间就可以构建对象,对象地址在全局数据区并构建完成
int main() {
	printf("%d %d %d %d %d\n",arr_ce[0],arr_ce[1],arr_ce[2],arr_ce[3],arr_ce[4]);
//constexpr 修饰的函数,也可以在运行期工作. 如下:
//在debug版本下, 你可以单步调试程序.
//在release版本下, 函数名称及函数结构被优化掉了.
//我以为它会优秀的把计算结果直接填进去,
//实际上也不是 ,还是有计算的过程.  
//它把计算代码穿插到了调用代码中.
	unsigned long int i;
	for(i=0;i<5;i++)
	{ //注意这里,release 版直接把函数代码穿插进来
		arr_n[i]=F(i);
	}

	constexpr Point p2(3, 4);  // 放到这里用constexpr 修饰就有点不伦不类,也可以这样做.
	printf("p1:%p,p2:%p\n",&p1,&p2);
	return 0;
}

//constexpr 还可以修饰构造函数,成员函数. 意味着这些函数在编译期间就可以调用.

4. constexpr 修饰的变量, 可以调用constexpr 修饰的函数来赋值

//直接得到常量数据

constexpr int arr_ce[5]={

F(0),F(1),F(2),F(3),F(4)

};

如果没有constexpr 来修饰F(), 这样的书写方式是不允许的.

error: call to non-'constexpr' function 'int F(int)'

我们看一下arr_ce[5]的数值

其对应的反汇编代码.

printf("%d %d %d %d %d\n",arr_ce[0],arr_ce[1],arr_ce[2],arr_ce[3],arr_ce[4]);

0x0000555555555155 <+12>: mov $0x3,%edi

0x000055555555515a <+17>: mov $0x2,%esi

0x000055555555515f <+22>: mov $0x1,%ecx

0x0000555555555164 <+27>: mov $0x1,%edx

0x0000555555555169 <+32>: mov $0x0,%eax

0x000055555555516e <+37>: mov %edi,%r9d

0x0000555555555171 <+40>: mov %esi,%r8d

0x0000555555555174 <+43>: mov %eax,%esi

0x0000555555555176 <+45>: lea 0xea7(%rip),%rdi # 0x555555556024

0x000055555555517d <+52>: mov $0x0,%eax

0x0000555555555182 <+57>: callq 0x555555555050 printf@plt

arr_ce[] 的5个数据是 0 1 1 2 3

我们看到printf 的前6个参数

rdi -> 字符串地址

rsi -> 0

rdx -> 1

rcx -> 1

r8 -> 2

r9 -> 3

说明打印时直接传递了数据.

忘了,原来直接看数组的内容才更简洁.

(gdb) p arr_ce

$1 = {0, 1, 1, 2, 3}

5. constexpr 修饰的函数怎样调试呢?

constexpr 函数也是我们自己写的, 那么多代码,运行出错了怎么办?

constexpr 修饰的函数,也可以在运行期工作. 例如我们在main函数中调用它,

把它的数值付给一个非constexpr 的数据, debug 版本下可以跟入函数来调试.

不过constexpr 修饰的函数,按说不应该在运行期调用, 否则你不如去掉constexpr 修饰.

6. constexpr 修饰的函数, 在release版本下是怎样工作的?

这应该属于进阶分析了. 因为constexpr 修饰的函数,不建议在运行期调用,

但我调用了,会是什么情况呢?更何况谁会保证不会穿插调用呢.

我试了一下,调用没问题, 但奇怪的是release 版本下怎么看不到 被调用的函数名呢?

是的,你用nm 工具确实查不到constexpr 修饰的函数, 我用反汇编分析了一下,发现gcc把

constexpr 修饰的函数直接优化穿插到调用代码中来了,gcc很神奇, 看下边汇编分析

cpp 复制代码
int arr[5];
int main() {
	unsigned long int i;
	for(i=0;i<sizeof(arr)/sizeof(arr[0]);i++)
	{ //注意这里, release 版直接把代码穿插进来,没有对应的F()代码.
		arr[i]=F(i);
	}
	return 0;
}

0000000000001040 <main>:
    1040:	f3 0f 1e fa          	endbr64 
    1044:	41 55                	push   %r13
    1046:	31 c0                	xor    %eax,%eax
    1048:	41 54                	push   %r12
    104a:	45 31 e4             	xor    %r12d,%r12d
    104d:	55                   	push   %rbp
    104e:	53                   	push   %rbx
    104f:	31 db                	xor    %ebx,%ebx
    1051:	48 83 ec 08          	sub    $0x8,%rsp              //调整堆栈,开辟局部空间
    1055:	4c 8d 2d c4 2f 00 00 	lea    0x2fc4(%rip),%r13        # 4020 <arr>
    105c:	0f 1f 40 00          	nopl   0x0(%rax)
    1060:	44 01 e0             	add    %r12d,%eax			  // F(n-1) + F(n-2)
    1063:	41 89 44 9d 00       	mov    %eax,0x0(%r13,%rbx,4)  //保留ebx 到 arr[i]
    1068:	48 83 c3 01          	add    $0x1,%rbx
    106c:	48 83 fb 05          	cmp    $0x5,%rbx			//i==5
    1070:	74 2e                	je     10a0 <main+0x60>     //退出循环
    1072:	89 dd                	mov    %ebx,%ebp
    1074:	45 31 e4             	xor    %r12d,%r12d
    1077:	b8 01 00 00 00       	mov    $0x1,%eax
    107c:	48 83 fb 01          	cmp    $0x1,%rbx           // i==1?
    1080:	74 de                	je     1060 <main+0x20>    //进行处理
    1082:	8d 7d ff             	lea    -0x1(%rbp),%edi
    1085:	e8 16 01 00 00       	callq  11a0 <_Z7Fabnacii>  //递归恢复堆栈
    108a:	41 01 c4             	add    %eax,%r12d          //r12d 为F(n-1)
    108d:	89 e8                	mov    %ebp,%eax           //ebp 是保留的i
    108f:	83 e8 02             	sub    $0x2,%eax          // i==2 ?
    1092:	74 cc                	je     1060 <main+0x20>   //进行处理
    1094:	bd 02 00 00 00       	mov    $0x2,%ebp
    1099:	83 f8 01             	cmp    $0x1,%eax          // i==3 ?时
    109c:	75 e4                	jne    1082 <main+0x42>   // i==其它时的处理
    109e:	eb c0                	jmp    1060 <main+0x20>   //进行处理
    10a0:	48 83 c4 08          	add    $0x8,%rsp       //恢复堆栈
    10a4:	31 c0                	xor    %eax,%eax       //返回0
    10a6:	5b                   	pop    %rbx            //恢复保留的寄存器
    10a7:	5d                   	pop    %rbp
    10a8:	41 5c                	pop    %r12
    10aa:	41 5d                	pop    %r13
    10ac:	c3                   	retq   
    10ad:	0f 1f 00             	nopl   (%rax)

7. constexpr 还可以修饰构造函数,成员函数.

意味着这些函数在编译期间就可以调用.

7.1 在常量表达式函数中你不能调用printf 函数

struct Point {

constexpr Point(int x, int y) : x(x), y(y) {printf("Point construct\n");}

int x, y;

};

实例化结构变量时会有错误. 说printf 不是常量表达式.

error: 'printf(((const char*)"Point construct\012"))' is not a constant expression

常量表达式是在编译时就能确定的表达式. printf 是运行时的调用函数,其执行由调用参数及系统环境来确定

所以常量表达式函数中不能调用 printf 函数.

7.2 在运行函数中调用constexpr 对象

// 把constexpr 函数放到main() 下执行也是可以的,尽管觉得有点不伦不类.

35 constexpr Point p2(3, 4);

0x00000000004011d2 <+124>: movl $0x3,-0x10(%rbp)

0x00000000004011d9 <+131>: movl $0x4,-0xc(%rbp)

此是gcc 把代码编译成 对象初始化参数直接存入对象的成员地址. 很简明, 没有所谓的显示调用构造函数的过程.

与release版调用constexpr 函数优化穿插代码类似.

8 小结:

算入门介绍吧. 介绍了constexpr 修饰全局变量(当然局部变量也可以),修饰函数,修饰成员函数,修饰构造函数的

使用场景,并给出了实例和实现分析. 原来其好处是gcc 在生成运行代码时,先自己一通运算把能确定下来的东西先确定下来.

后面还有针对模板的使用下期介绍.

相关推荐
Smile丶凉轩3 小时前
Qt 界面优化(绘图)
开发语言·数据库·c++·qt
small_wh1te_coder4 小时前
从经典力扣题发掘DFS与记忆化搜索的本质 -从矩阵最长递增路径入手 一步步探究dfs思维优化与编程深度思考
c语言·数据结构·c++·stm32·算法·leetcode·深度优先
虾球xz7 小时前
游戏引擎学习第282天:Z轴移动与摄像机运动
c++·学习·游戏引擎
.小墨迹8 小时前
Apollo学习——planning模块(3)之planning_base
linux·开发语言·c++·学习·自动驾驶
龙湾开发8 小时前
计算机图形学编程(使用OpenGL和C++)(第2版)学习笔记 10.增强表面细节(一)过程式凹凸贴图
c++·笔记·学习·3d·图形渲染
德亦周8 小时前
如何在Mac电脑上的VScode去配置C/C++环境
c++·vscode·macos
XiaoyaoCarter8 小时前
每日一道leetcode(新学数据结构版)
数据结构·c++·算法·leetcode·职场和发展·哈希算法·前缀树
八月的雨季 最後的冰吻9 小时前
SIP协议栈--osip源码梳理
linux·服务器·网络·c++·网络协议
fancy1661669 小时前
搜索二维矩阵 II
c++·算法·矩阵