c++中的enum变量 和 constexpr说明符
author: hjjdebug
date: 2025年 04月 23日 星期三 13:40:21 CST
description: c++中的enum变量 和 constexpr说明符
文章目录
- 1.Q:enum 类型变量可以有++,--操作吗?
- 1.1补充: c/c++中enum的另一个细微差别.
- 2.Q: constexpr 修饰的函数,要求传入的参数必需是常量吗?
- 3. Q constexpr 编译期求值真正的意思是什么?
- 3.1 debug 版本, constexpr 修饰无效
- 3.2 release 版本, constexpr 函数被优化掉了
- 4. constexpr 中碰到了一个左值引用绑定问题
- 5 x86-64 linux系统上函数调用传参约定
本来应该分2篇博客写的,放一起吧,也有相关性,都是c++的.
1.Q:enum 类型变量可以有++,–操作吗?
A: c支持,c++不支持.
c++中的enum 类型变量和c中的enum变量基本含义相同,但又略有区别.
试验代码:
$ cat main.c
#include <stdio.h>
typedef enum _ABC { E0,E1,E2}ABC;
int main()
{ABC a=E0;a++;return 0;
}
c语言是支持enum类型变量++或–这种操作的,因为它天然认为enum类型就是整形
将main.c改名为main.cpp文件,则编译不通过
给出的编译错误为:
error: no ‘operator++(int)’ declared for postfix ‘++’ [-fpermissive]
没有后++的操作运算符说明, operator++(int), 不允许的操作.
就是说c++编译器认为enum 类型是一种新的类型,并不是整型,虽然它的内部实现是把它当整形来实现的.
那如何解除这种限制呢?
如下:多加一行代码, 重载 ABC operator++(int)类型的函数. 就可以对ABC 型变量执行后++操作
$cat main.cpp
#include <stdio.h>
typedef enum _ABC { E0,E1,E2}ABC;
constexpr ABC operator++(ABC d,int) {return ABC((int)d+1);}
int main()
{ABC a=E0;a++;return 0;
}
1.1补充: c/c++中enum的另一个细微差别.
c++中可以不用typedef 重定义类型,而直接使用enum 就可以了.如下:
enum ABC {E0,E1,E2}, 其它代码不动.
而c语言不行,
如果去掉typedef 重定义,你需要在声明变量时加上enum声明,如下:
enum ABC a=E0
否则会有编译错误,如下:
error: unknown type name ‘ABC’; use ‘enum’ keyword to refer to the type
| ABC a=E0;
| ^~~
2.Q: constexpr 修饰的函数,要求传入的参数必需是常量吗?
答: 不是
这个constexpr修饰的函数,网上说是可以编译期求值,要求传入常量值.
我看说法不完全正确,这里我传入a就是内存变量,不是常量
我还可以写如下代码:
for(int i=0;i<2;i++)
{
a++; // 这个a 是不断变化的.
printf(“a is %d\n”, (int)a);
}
3. Q constexpr 编译期求值真正的意思是什么?
A: constexpr函数并不会进入执行文件中,执行文件中没有这个函数.
正确的理解constexpr的编译期求值, 是它在运行时不求值,
就是说它已经做到了运行时, 不进入你定义的constexpr函数,
例如对该例,它的a++就直接变成了整数的a++
并不是说一定要传入常量值.
3.1 debug 版本, constexpr 修饰无效
这只是我的一种猜测,后面我会证明,
如果编译成debug版调试程序,它总是会跟入函数的, 这不是constexpr 的初衷
对debug版本,加不加constexpr 效果是一样的.
3.2 release 版本, constexpr 函数被优化掉了
那么对release 版是否真的把constexpr函数给优化掉了呢?那要反汇编代码了.
开干吧! 源代码如下:
#include <stdio.h>
typedef enum _ABC { E0,E1,E2}ABC;
constexpr ABC& operator++(ABC& d,int) {return d=ABC((int)d+1);}
int main()
{ABC a=E0;for(int i=0;i<5;i++){a++;}return 0;
}
release 版反编译代码
我惊讶的发现, 我的main函数被它优化成空函数了
如下:
0000000000001040 :
1040: f3 0f 1e fa endbr64
1044: 31 c0 xor %eax,%eax
1046: c3 retq
1047: 66 0f 1f 84 00 00 00 nopw 0x0(%rax,%rax,1)
什么for循环, enum 变量,统统都是空.
哇! 这么利害,把代码全优化掉了,它认为我这些代码都是没用的东西.
是的,如果把main也当成一个一般的子函数看,它除了占用点cpu资源,对外界就没有什么影响.
我们加上printf 代码,让代码变得有用起来. 不能让它全优化了.
#include <stdio.h>
typedef enum _ABC { E0,E1,E2}ABC;
constexpr ABC& operator++(ABC& d,int) {return d=ABC((int)d+1);}
int main()
{ABC a=E0;for(int i=0;i<5;i++){a++;printf("a is %d\n",(int)a);}return 0;
}
release 版反编译代码
0000000000001060 <main>:1060: f3 0f 1e fa endbr64 1064: 55 push %rbp //rbp 如栈,框架1065: 48 8d 2d 98 0f 00 00 lea 0xf98(%rip),%rbp # 2004 <_IO_stdin_used+0x4>106c: 53 push %rbx //rbx 入栈,它会做为a变量106d: bb 01 00 00 00 mov $0x1,%ebx //ebx 就是a变量,初始值给了1,因为它知道第一次打印值是1,利害!1072: 48 83 ec 08 sub $0x8,%rsp //调整堆栈,为局部变量准备空间1076: 89 da mov %ebx,%edx // a变量送给edx, printf 的第二个参数1078: 48 89 ee mov %rbp,%rsi // rsi, printf 的第一个参数,字符串地址107b: bf 01 00 00 00 mov $0x1,%edi // rdi=1,向stdout输出,因为它调用的是__printf_chk1080: 31 c0 xor %eax,%eax // 表示传递的浮点数个数是0个1082: e8 c9 ff ff ff callq 1050 <__printf_chk@plt>1087: 83 c3 01 add $0x1,%ebx // ebx 为a变量//它算出了a变量和i变量的对应关系,所以优化掉i变量,让a变量与6比,果然很智能!108a: 83 fb 06 cmp $0x6,%ebx 108d: 75 e7 jne 1076 <main+0x16> //循环到1076108f: 48 83 c4 08 add $0x8,%rsp //局部空间栈恢复1093: 31 c0 xor %eax,%eax //返回值1095: 5b pop %rbx //恢复rbx1096: 5d pop %rbp //恢复rbp1097: c3 retq //函数返回 1098: 0f 1f 84 00 00 00 00 nopl 0x0(%rax,%rax,1)109f: 00
完美验证了我的设想!constexpr 函数优化后不会出现在运行期,但它也不会要求参数必需是常量,变量也行,只要能被它替代就行.
4. constexpr 中碰到了一个左值引用绑定问题
代码:
constexpr ABC& operator++(ABC& d,int) {return ABC((int)d+1);}
error: cannot bind non-const lvalue reference of type ‘ABC&’ {aka ‘_ABC&’} to an rvalue of type ‘ABC’ {aka ‘_ABC’}
错误是说: 不能够让非-const 的左值ABC& 类型与右值 ABC类型相绑定.
就是说要返回引用,而不要返回值
修改方法.
返回一个值的引用. 一个变量的别名叫引用,引用在函数间传递的是地址,该函数实际的意义就是修改自身.
所以函数内把值再变成引用.如下:添加d=内容,把值送给引用.
constexpr ABC& operator++(ABC& d,int) {return d=ABC((int)d+1);}
5 x86-64 linux系统上函数调用传参约定
采用System V AMD64 调用约定.
前六个整型参数(包括指针)通过寄存器传递
顺序RDI、RSI、RDX、RCX R8 R9 ,超过6个多余的用堆栈传,从右向左顺序压入堆栈.
例: test(0,1,2,3,4,5,6) 7个参数
31 ff xor %edi,%edi
be 01 00 00 00 mov $0x1,%esi
ba 02 00 00 00 mov $0x2,%edx
b9 03 00 00 00 mov $0x3,%ecx
41 b8 04 00 00 00 mov $0x4,%r8d
41 b9 05 00 00 00 mov $0x5,%r9d
6a 06 pushq $0x6
//test 函数c++中变成了_Z4Testiiiiiii,名称带参数类型,_Z是gcc的函数标志,4是名称长度,7个i是7个整形参数
e8 04 01 00 00 callq 1190 <_Z4Testiiiiiii>