---
# System prepended metadata

title: extern "C" 如何使系統函式庫兼容C與C++
tags: [github]

---

---
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). ⟪程序員的自我修養⟫**
是我自己閱讀消化之後所做之筆記。