今天做了一個錯誤的決定, 想說在一堆 C 程式裡呼叫另一堆 C++ 程式。邊弄邊學, 最後發現什麼都沒改的情況, 改用 g++ link 原本的 C 程式就會爆炸。想想還是撿要用的一小部份程式出來, 另寫 C 的程式比較單純。不過也藉這機會, 才讓我真的搞懂這之中發生什麼事。
先附上要用的範例, 再來慢慢廢話, 沒耐心的人直接玩範例可能就懂了。
原始碼
/* b.h */ #ifndef _B_H_ #define _B_H_ #ifdef __cplusplus extern "C" { #endif int add(int a, int b); #ifdef __cplusplus } #endif #endif
/* b.cpp */ #include "b.h" int add(int a, int b) { return a + b; }
/* a.c */ #include <stdio.h> #include "b.h" int main(void) { printf("%d\n", add(3, 5)); return 0; }
編譯方式
$ gcc -c a.c $ g++ -c b.cpp $ g++ a.o b.o -o a $ ./a # 輸出 8
說明
《How to mix C and C++ Updated! , C++ FAQ》將 C 和 C++ 互相呼叫的各式注意事項寫得相當清楚, 不愧為 FAQ 啊! 其中在 C 呼叫 C++ 的函式時, 要注意幾點 C++ 有 name mangling, 也就是不管是 class method 還是 function, 名稱 X 編出來都不會是 X, 而 C 通常沒有 name mangling。不過不管 C 是否有 name mangling, 關鍵是讓 C 能用 C 的方式認得 C++ 的 symbol。
先明白編譯、連結時發生了什麼事, 從這裡出發, 可知要讓 C 呼叫到 C++ 的函式, 要從幾個地方下手:
- C、C++ 都需要它們各自的 header, 才能編成 object code。編譯時 symbol 仍不用存在
- 用 gcc 編 C 程式; 用 g++ 編 C++ 程式。雖說是很自然的事, 對兩者都不熟的我, 一開始還真沒想到。
- 連結 object code 時, C 要能找到 C++ 的函式
- 既然要連結 C++ 的 object code, 連結時自然要用 g++
回到前面的範例:
- 寫 b.h 時用 #ifdef __cplusplus 讓 a.c 和 b.cpp 都能 include b.h, 且看到不同的東西。可用 gcc -E、g++ -E 查看前置處理器展開的結果, 見證騙術的最高境界, 同時滿足兩方的需求, 而且還沒有查覺到對方的存在!!
- 編譯 b.cpp 時, 透過 b.h 加入 extern "C", 這樣才不會做 name mangling。用 nm b.o 查看, 會發現有 name mangling 時是 _Z3addii, 反之則是 add (ps. 經 Scott 提醒, 可用 c++filt demangle symbol)
- 由於 C 沒有 extern "C" 的語法, b.h 裡面有這段的話會編譯錯誤, 透過 ifdef 避開。編譯 a.c 沒什麼特別的, 騙它有 add 這個 symbol 即可。
連結時很單純, 先前在編 .o 時確保 b.o 裡可以找到 a.o 認得的名稱 add, 再來記得只有 g++ 同時認得 a.o 和 b.o 的格式, 就沒什麼問題了。
另外, 若想用 C++ 的 class method, 得先包成 C++ function 才行。傳遞物件的話, 參考 FAQ 的說明, 有些注意事項, 待要用到時再來細看。
2012-01-30 更正
gcc 和 g++ 都是呼叫 ld, 只是傳的參數不一樣, 我原本的說法「只有 g++ 同時認得 a.o 和 b.o 的格式」並不正確。還沒有仔細研究 ld, 推測 ld 應該沒有區別 object file 是怎麼被產生的。
1. c++filt 可解 C++ name mangling:
回覆刪除$ c++filt _Z3addii
add(int, int)
2. "既然要連結 C++ 的 object code, 連結時自然要用 g++"
你的範例用 gcc 連結也可。
g++ 預設連結 -lstdc++ 所以一般確實想用它
原來如此, 又多學到相關工具了, 謝啦
回覆刪除