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 函數優化后不會出現在運行期,但它也不會要求參數必需是常量,變量也行,只要能被它替代就行.
總結一下: constexpr 函數是一種可以被優化掉的函數,就是說它不會生成對應的函數代碼,而是會把返回值嵌入到你調用的代碼中. 返回值可能是一個常數或常量,也可以是一個變量的簡單運算.就是一個變量加個偏移,乘一個數除一個數等.一般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>