對於使用 const
還是 #define
來定義常數值, 我想你可以找到許多討論, 這篇文章並不是要來下定論, 而是要透過編譯器輸出的組合語言, 來看兩者的差別, 以下的比較都是以 GCC 預設的選項編譯, 沒有加上特別的最佳化選項。
使用 #define
以下是一個簡單的 C 程式:
#define a 10
int b;
int main(void) {
b = a;
}
接著我們使用 Compiler Explorer 觀察編譯器的組合語言輸出結果如下:
這個工具很有意思, 可以讓你對照左邊原始碼觀察右邊編譯器輸出的組合語言, 將游標移到原始檔的某一行, 右邊就會以深藍色標示對應該行產生的組合語言, 像是上圖中右邊深藍色區域顯示的就是對應左邊 b = a
這一行的組合語言。如果你對於 x86 的組合語言沒有概念, 可以參考這篇簡介。
以下是右邊窗格內的組合語言 (我顯示的是 ATT 格式的語法, 如果你比較習慣看 Intel 格式的語法, 可以把右邊窗格上方的 -masm=att
刪除或是改為 -masm=intel
):
b:
.zero 4
main:
pushq %rbp
movq %rsp, %rbp
movl $10, b(%rip)
movl $0, %eax
popq %rbp
ret
你可以看到因為 #define
會在編譯前進行替換, 實際上並不會定義變數, 所以在程式開頭只會看到變數 b
被定義為 4 位元組的 0。而對應 b = a
的組合語言是這行:
movl $10, b(%rip)
它會直接把 10 搬入 b
所在的位址。
使用 const
我們把一樣的程式改成使用 const
:
const int a = 10;
int b;
int main(void) {
b = a;
}
同樣採用 Compiler Explorer 觀察編譯器輸出的組合語言結果如下:
以下就是右邊窗格的內容:
a:
.long 10
b:
.zero 4
main:
pushq %rbp
movq %rsp, %rbp
movl $10, %eax
movl %eax, b(%rip)
movl $0, %eax
popq %rbp
ret
你會發現因為 const
會定義變數, 所以程式一開頭可以看到變數 a
, 並且數值是 10。而對應到 b = a
這一行的組合語言變成這樣:
movl $10, %eax
movl %eax, b(%rip)
第一個特別的地方是, 因為 a
是 const
, 所以它的值不會變, 因此實際上產生的組合語言根本不會到 a
所在的位址取值, 而是直接使用 $10
。雖然如此, 實際將 10 搬到變數 b
的動作卻還是透過中介的 eax
暫存器, 由兩個指令完成。
空間與時間的差異
從上述的組合語言輸出來看, 使用 const
的版本不但耗費了空間放置變數 a
, 而且執行時也多了一道指令, 這道指令不但會佔用空間放置指令碼, 執行時也會耗掉時間。以下是我在 Debian Linux 上 GCC 10.2.1 編譯的結果:
$ ls -l const define
-rwxr-xr-x 1 meebox meebox 16512 May 8 20:18 const
-rwxr-xr-x 1 meebox meebox 16488 May 8 20:18 define
單純就這個例子而言, 你可以看到 const
的版本比 #define
的版本肥了 24 個位元組, 這對於一般電腦上的程式可能無所謂, 但如果是在一些嵌入式應用場合使用的小晶片上, 空間與時間都錙銖必較, 可能就不會考量 const
所帶來的好處, 改用 #define
了。
透過這樣的工具, 就可以針對相同功能但不同寫法的程式觀察實際編譯後的差異, 再決定使用哪一種寫法。
Top comments (0)