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_ce5={1,2,3,4,5};

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

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

int arr_ce5={1,2,3,4,5};

是的, 这样也声明并初始化了变量arr_ce5, 但它们没有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_ce5={

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

};

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

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

我们看一下arr_ce5的数值

其对应的反汇编代码.

printf("%d %d %d %d %d\n",arr_ce0,arr_ce1,arr_ce2,arr_ce3,arr_ce4);

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 在生成运行代码时,先自己一通运算把能确定下来的东西先确定下来.

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

相关推荐
·薯条大王17 小时前
经济实惠玩云服务器|一台云服务器多人共用,子账号配置教程
java·linux·运维·服务器·汇编·c++·python
hansang_IR21 小时前
【题解】LC:倍增 / 区间并查集(Range Parallel Unionfind)
c++·算法·并查集
东东最爱敲键盘21 小时前
day7.c++继承
c++
沫璃染墨1 天前
《从零入门Linux系统篇(十五):系统工具篇·六——GDB调试器详解:从程序执行控制到高级调试技巧》
linux·运维·服务器·c语言·c++
吃着火锅x唱着歌1 天前
Effective C++ 学习笔记 条款40 明智而审慎地使用多重继承
c++·笔记·学习
醉城夜风~1 天前
(超详细)C++ STL list容器详解:从使用方法到双向链表底层原理全面解析
c++·windows
烟花落o1 天前
C++ 从 C 到进阶 ②:引用、inline 、nullptr
c++·引用·inline·nullptr·c++入门
liulilittle1 天前
MOE路由:路由(logits: top-k/8)
c++·人工智能·算法·机器学习·llm
旖旎夜光1 天前
LeetCode 11:盛最多水的容器(双指针问题) —— 题解
数据结构·c++·算法·leetcode·双指针
我不会插花弄玉1 天前
15.map,set下:AVL树,红黑树和map,set的封装【由浅入深-C++】
c++·stl