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

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

相关推荐
木子.李34729 分钟前
排序算法总结(C++)
c++·算法·排序算法
freyazzr2 小时前
C++八股 | Day2 | atom/函数指针/指针函数/struct、Class/静态局部变量、局部变量、全局变量/强制类型转换
c++
fpcc2 小时前
跟我学c++中级篇——理解类型推导和C++不同版本的支持
开发语言·c++
终焉代码3 小时前
STL解析——list的使用
开发语言·c++
DevangLic3 小时前
【 *p取出内容 &a得到地址】
c++
鑫鑫向栄4 小时前
[蓝桥杯]修改数组
数据结构·c++·算法·蓝桥杯·动态规划
鑫鑫向栄4 小时前
[蓝桥杯]带分数
数据结构·c++·算法·职场和发展·蓝桥杯
m0_552200824 小时前
《UE5_C++多人TPS完整教程》学习笔记37 ——《P38 变量复制(Variable Replication)》
c++·游戏·ue5
小wanga4 小时前
【递归、搜索与回溯】专题三 穷举vs暴搜vs回溯vs剪枝
c++·算法·机器学习·剪枝
Code_流苏5 小时前
C++课设:简易日历程序(支持传统节假日 + 二十四节气 + 个人纪念日管理)
开发语言·c++·stl容器·课设·期末大作业·日历程序·面向对象设计