--- tags: github --- > New website: [https://rhythm16.github.io](https://rhythm16.github.io) # extern "C" 如何使系統函式庫兼容C與C++ ## 原始的問題 故事是這樣的: 如果我們有個C函式庫,裡面有函數例如: ```clike /* cfunc.c */ void awesome_C_function() { /* does nothing */ } ``` 你可能會想說:C函式庫,C++程式應該也可以用吧 ```cpp /* main.cpp */ #include <iostream> void awesome_C_function(); int main() { awesome_C_function(); return 0; } ``` 那編譯看看: ```bash $ gcc -c cfunc.c $ g++ -c main.cpp $ g++ main.o cfunc.o -o a.out main.o: In function `main`: main.cpp:(.text+0x5): undefined reference to `awesome_C_function()` collect2: error: ld returned 1 exit status ``` 問號??為什麼會這樣勒 原因是g\++在編譯C++程式時,會對符號(變數與函數名稱等等)進行符號修飾([name mangling](https://en.wikipedia.org/wiki/Name_mangling)),導致鏈接器在鏈接時找不到對應的符號名稱,對應到最後一行`collect2: error: ld returned 1 exit status` 如果使用`readelf`工具檢視各個編譯出的.o檔的符號表,就可以看出端倪:(注意不同版本之編譯器、作業系統結果可能不完全一樣) main.o: ```bash $ readelf -s main.o #-s表示顯示符號表 Symbol table `.symtab' contains 21 entries: Num: Value Size Type Bind Vis Ndx Name ... 16: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _Z18awesome_C_functionv ... ``` cfunc.o: ```bash $ readelf -s cfunc.o Symbol table `.symtab' contains 21 entries: Num: Value Size Type Bind Vis Ndx Name ... 8: 0000000000000000 0 FUNC GLOBAL DEFAULT 1 awesome_C_function ... ``` 雖然我們source code都是寫`awesome_C_function()`,因為C++有符號修飾,變成了兩個不同名字的符號,產生錯誤。 ## extern "C" 如何解決問題 好在,C++有`extern "C"`語法,可以定義C符號,使其不做符號修飾,若修改main.cpp成 ```cpp /* main.cpp */ #include <iostream> extern "C" { void awesome_C_function(); } int main() { awesome_C_function(); return 0; } ``` 則程式可正常編譯,執行: ```bash $ g++ -c main.cpp $ g++ main.o cfunc.o -o a.out $ ./a.out $ ``` 太好了,問題乍看之下解決了,不過如果我們想把函式庫標頭另外放在一個檔案,供人`#include`,會出現C不支援`extern "C"`的問題,也就是說如果我們這樣寫awesome.h: ```cpp /* awesome.h */ extern "C" { void awesome_C_function(); } ``` main.cpp: ```cpp /* main.cpp */ #include <iostream> #include "awesome.h" int main() { awesome_C_function(); return 0; } ``` 上面的main.cpp使用g++編譯完全沒有錯誤,但如果今天想寫C,並使用`awesome.h`函式庫的話: ```clike /* main.c */ #include "awesome.h" int main() { awesome_C_function(); return 0; } ``` 使用gcc編譯會直接報錯,錯誤內容就不貼出了,反正gcc在抱怨他根本看不懂include進來的extern "C"語法啊,那是C++專屬的。 難道一個編譯好的C函式庫,要配套兩種標頭檔嗎,一個給C用,一個給C\++用? 答案是不需要,使用編譯前的預處理器可以幫我們解決問題! 在編譯C\++程式時,C++的編譯器會定義"__cplusplus"這個macro,所以只要把awesome.h寫成這樣就解決問題了。 ```cpp /* awesome.h */ #ifdef __cplusplus extern "C" { #endif void awesome_C_function(); #ifdef __cplusplus } #endif ``` 如此一來,無論是C還是C++程式,都可以`#include "awesome.h"`了,預處理器會自動的依情況展開,編譯、鏈接都會成功。 這樣的技巧再幾乎所有的系統標頭檔都有用到。 本文內容來自 **俞甲子、石凡、潘愛民 (2009). ⟪程序員的自我修養⟫** 是我自己閱讀消化之後所做之筆記。
×
Sign in
Email
Password
Forgot password
or
Sign in via Google
Sign in via Facebook
Sign in via X(Twitter)
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
Continue with a different method
New to HackMD?
Sign up
By signing in, you agree to our
terms of service
.