從 C 呼叫 C++ 函式的過程理解程式編譯、連結的原理

fcamel
3 min readAug 17, 2018

--

承接上篇文章,這篇用 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;
}

編譯和執行

$ g++ -c a.c
$ g++ -c b.cpp
$ g++ a.o b.o -o a
$ ./a # 輸出 8
# 查看 symbol
$ nm b.o
0000000000000000 T add
$ nm a.o
U add
0000000000000000 T main
U printf

說明

《How to mix C and C++ Updated! , C++ FAQ》說明 C 和 C++ 互相呼叫的注意事項。其中在 C 呼叫 C++ 的函式時,要注意 C++ 有 name mangling,不管是 class method 還是 function,名稱 X 編出來都不會是 X,所以要用 extern “C” 避免 name mangling,這樣 C 的程式和 C++ 的程式都能認得它們用的 symbol。

細節說明如下:

  • 寫 b.h 時用 #ifdef __cplusplus 讓 a.c 和 b.cpp 都能 include b.h,且看到不同的東西。可用 g++ -E 查看前置處理器展開的結果。
  • 編譯 b.cpp 時,透過 b.h 加入 extern “C”,這樣就不會做 name mangling 。上例 add() 在有 name mangling 的情況,產生的 symbol 是_Z3addii,沒有name mangling 則是 add。附帶一提,可用 nm -C 直接輸出 demangle 的名稱,或用 c++filt STRING 輸出 demangle 的名稱。
  • 由於 C 沒有 extern “C” 的語法, b.h 裡面有這段的話會編譯錯誤要透過 #ifdef 避開。
  • 從 linker 的角度來看,a.o 用到 add,而 b.o 準備了 add,沒有問題,可以產生執行檔。

另一方面,若保留 name mangling,結果如下:

# 查看有 name mangling 的 symbol
$ g++ -U __cplusplus -c b.cpp
$ nm b.o
0000000000000000 T _Z3addii
$ nm -C b.o
0000000000000000 T add(int, int)
$ g++ a.o b.o -o a
a.o: In function `main':
a.c:(.text+0xf): undefined reference to `add'
collect2: error: ld returned 1 exit status
  • g++ -U __cplusplus 取消 #define __cpluscplus, 所以 add 的宣告沒有包在 extern “C” 內。編譯函式 add 後產生的 symbol 有 mangling。
  • 結果 linker 發現 a.o 要呼叫 add,但 b.o 內是 _Z3addii,無法滿足 a.o 的要求,產生 linking error。

--

--