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

今天做了一個錯誤的決定, 想說在一堆 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 是怎麼被產生的。