DEV Community

codemee
codemee

Posted on • Updated on

用工具觀察 const 與 #define 的基本差異

#c

對於使用 const 還是 #define 來定義常數值, 我想你可以找到許多討論, 這篇文章並不是要來下定論, 而是要透過編譯器輸出的組合語言, 來看兩者的差別, 以下的比較都是以 GCC 預設的選項編譯, 沒有加上特別的最佳化選項。

使用 #define

以下是一個簡單的 C 程式:

#define a 10
int b;

int main(void) {
    b = a;
}
Enter fullscreen mode Exit fullscreen mode

接著我們使用 Compiler Explorer 觀察編譯器的組合語言輸出結果如下:

define

這個工具很有意思, 可以讓你對照左邊原始碼觀察右邊編譯器輸出的組合語言, 將游標移到原始檔的某一行, 右邊就會以深藍色標示對應該行產生的組合語言, 像是上圖中右邊深藍色區域顯示的就是對應左邊 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
Enter fullscreen mode Exit fullscreen mode

你可以看到因為 #define 會在編譯前進行替換, 實際上並不會定義變數, 所以在程式開頭只會看到變數 b 被定義為 4 位元組的 0。而對應 b = a 的組合語言是這行:

        movl    $10, b(%rip)
Enter fullscreen mode Exit fullscreen mode

它會直接把 10 搬入 b 所在的位址。

使用 const

我們把一樣的程式改成使用 const

const int a = 10;
int b;

int main(void) {
    b = a;
}
Enter fullscreen mode Exit fullscreen mode

同樣採用 Compiler Explorer 觀察編譯器輸出的組合語言結果如下:

const

以下就是右邊窗格的內容:

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
Enter fullscreen mode Exit fullscreen mode

你會發現因為 const 會定義變數, 所以程式一開頭可以看到變數 a, 並且數值是 10。而對應到 b = a 這一行的組合語言變成這樣:

        movl    $10, %eax
        movl    %eax, b(%rip)
Enter fullscreen mode Exit fullscreen mode

第一個特別的地方是, 因為 aconst, 所以它的值不會變, 因此實際上產生的組合語言根本不會到 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
Enter fullscreen mode Exit fullscreen mode

單純就這個例子而言, 你可以看到 const 的版本比 #define 的版本肥了 24 個位元組, 這對於一般電腦上的程式可能無所謂, 但如果是在一些嵌入式應用場合使用的小晶片上, 空間與時間都錙銖必較, 可能就不會考量 const 所帶來的好處, 改用 #define 了。

透過這樣的工具, 就可以針對相同功能但不同寫法的程式觀察實際編譯後的差異, 再決定使用哪一種寫法。

Oldest comments (0)