連結指示 extern "C"

什麼叫做連結指示呢?一般來說,這是給linker看的,因為當我們把程式一各個模組都編譯完成之後,我們會透過linker將每個模組都連結起來,成為一個可以執行的檔,因此如果你要將不同語言的模組連結起來,那必須設定「連結指示」否則的話,它會找不到該模組,以致無法連結完成。

※ 連結的運作

C和C++的程式都是透過編輯、編譯、連結、執行等步驟來完成,但是通常我們只知道編譯是將程式碼編成組合語言,但是我們卻不知道連結的工作在做什麼,其實連結就是把編譯完成後的組合語言碼,使用「連結的標籤」讓它們合在一起,每個語言編譯的時候產生出來的連結標籤不同,因此我們該如何將它統一這就是「連結指示」要做的事情。

Example:

#include
int add(int a, int b)
{
return a+b;
}
int main()
{
int a = add(3,2);
}

gcc compiler

movl $3, (%esp)
call _add
movl %eax, -4(%ebp)

_add:
pushl %ebp
movl %esp, %ebp

g++ compiler

movl $3, (%esp)
call __Z3addii
movl %eax, -4(%ebp)

__Z3addii:
pushl %ebp
movl %esp, %ebp

以上的範例展示了,使用g++和gcc編譯之後的組合語言內容,我想你應該看的很清楚了,在gcc編譯完,呼叫函式的時候使用call _add但是g++編譯的結果卻是call __Z3addii,但是為什麼各別可以連結完成呢?因為它們各別的呼叫都符合它們所定義的label,所以可以各別的呼叫成功。

* call _add -> _add: (gcc compiler)
* call __Z3addii -> __Z3addii: (g++ compiler)

如此你應該了解它的重要性了,要是我們呼叫函式所對應的Label不存在的話,那麼我們的連結將會失敗,所以當使用不同的編譯器編出來,卻要讓它連結起來的時候,以上的觀念就非常的重要了,否則的話一定永遠連結失敗,因為根本找不到呼叫函式的定義在那裡,主程式只知道要call _add但是它需要去找_add這個Label。

有人說那我們從頭到尾都使用同一個編譯器不就好了,翻出來也都是一樣的,這個想法非常的不錯,但是某天你朋友寫了一個很強大的函式,且將它包成了一個函式庫,但是它使用C所寫成的,而你目前使用C++語言當開發工具,且使用g++當編譯工具,那你如何去呼叫C之中的函式呢?

難到 call __Z3addii (g++ compiler) -> _add: (gcc compiler) 這樣你會成功嗎?這個時候連結器一定會告訴你,找不到定義的函式內容。

※ 正確的觀念

那我們該怎麼做才可以去連結過去使用C所寫成的模組呢?而且我們使用C++語言當工具,那就只有一個辦法,那就是讓函式呼叫和函式定義一樣。

Example:

int add(int a, int b)
{
return a+b;
}

gcc compiler

_add:
pushl %ebp
movl %esp, %ebp

以上的範例告訴我們當模組寫好,且已編譯完成之後,它的部份組合語言是長這個樣子,特別注意它的Label是「_add:」這就是我們呼叫它時的進入點,這已經沒有辦法更改了,因為你朋友包給你的時候模組已經編譯完成,它就是長這個樣子,所以我們必須要想辦法讓g++ compiler編譯出來的呼叫函式為call _add:這樣,如此我們才可以正確的去呼叫到函式的定義,讓函式幫我們處理一些的工作。

但是剛剛我們就發現了使用g++ compiler編譯出來的結果是call __Z3addii根本就完全的不同,那我們如何去生出call _add呢?這個時候我們的「連結指示」就用的到啦。

※ 連結指示

它告訴我們目前使用的編譯器,將看到的程式內容使用「某個」語言來翻譯。

Example:

g++ compiler

extern "C"
{
int add(int, int);
}
int main()
{
cout << add(3,2) << endl;
return 0;
}

gcc compiler

int add(int a, int b)
{
return a+b;
}

extern "C" 可以告訴編譯器,將編譯的程式檔中所有的add函式都翻譯成相容於C的Label,如此編譯後的結果為call _add,這樣就符合我們呼叫C所寫成的函式庫中的Label了,這樣連結器做連結的時候可以很正確的找到函式的定義檔。

※ C++ call C

如果以上你都完全了解的話,那以下的範例是如何讓C++程式去呼叫C所寫的模組,而觀念就是我們上面所解釋的觀念。

Example:

cppfile.cpp

#include
extern "C"
{
#include "cfile.h"
}
using namespace std;
int main()
{
cout << sum(12,5) << endl;
return 0;
}

cfile.h

int sum(int, int);

cfile.c

#include "cfile.h"
int sum(int a, int b)
{
return a+b;
}

compiling and linking

g++ -c cppfile.cpp (compiler)
gcc -c cfile.c (compiler)
g++ -o cpp_call_c cppfile.o cfile.o (linker)

※ C call C++

有C++呼叫C的模組,那也有C去呼叫C++的模組啦,但是很少人這樣使用,因為結連的時候還是需要使用g++的編譯器才可以連結,因為gcc對於c++所寫成的模組根本就看不懂。

另外在C中是沒有連結指示的,所以當gcc看到extern "C"會出現錯誤,所以我們需要使用巨集使對於C它會隱藏,而對於C++它則會加入編譯條件。

Example:

#ifdef __cplusplus
extern "C" {
#endif
void show_name(char* name);
#ifdef __cplusplus
}
#endif

上面的範例會看到「__cplusplus」這個巨集,它在每個標準的C++編譯器都是預先定義好的,我們這邊做到條件的編譯,當使用C的 compiler它會隱藏extern "C"這個連結指示,但是如果使用C++編譯器的話,它會顯示出extern "C"這個連結指示。

以上的範例會產生什麼結果呢?將它使用gcc編譯的時候會產生_show_name:的Label,而使用g++編譯的時候也會產生 _show_name: 的Label,因為在g++編譯器已經有預先定義好的巨集__cplusplus所以會將連結指示也加入進去編譯,但是如果是gcc則不會,所以用在不同的編譯器,它所產生出來的呼叫Label和函式定義的Label都是一樣的,如此我們可以正確的將模組連結。

Example:

cfile.c

#include
#include "cppfile.h"
int main()
{
show_name("clockwork");
return 0;
}

cppfile.h

#ifdef __cplusplus
extern "C" {
#endif
void show_name(char* name);
#ifdef __cplusplus
}
#endif

cppfile.cpp

#include
#include
#include "cppfile.h"
using namespace std;
void show_name(char* name)
{
string str(name);
cout << "Hi!!" << str << endl;
}

compiling and linking

gcc -c cfile.c
g++ -c cppfile.c
g++ -o c_call_cpp cfile.o cppfile.o

2 意見:

    On 2011年1月10日 下午8:18 匿名 提到...

    您好:
    如你文章所提~當要使用其它編譯器編輯後的函式,則要使用連結指示.....
    不過我遇到的問題是~..
    來源碼都是c語言~但是卻發生linker error undefined reference to ""
    函數檔案我都有include進來..但還是發生錯誤~不知道是什麼原因..
    版主有遇過嗎??
    能否分享
    謝謝!!

     

    extern C只限在於c與c++之間的轉換,你發生的問題,很單純只是連結不到函式庫,如果你用的是win32平台上的IDE編輯器的話,那你要看其它網路的文章,若是在unix下的話,可以使用gcc -L./lib_path -llib_name舉個例子來說我用到了libmysql.a的函式庫裡面的東西,所以我在編譯聯結時,會這樣寫
    gcc -Wall -g -o test_mysql -I/usr/include/mysql/ -L/usr/lib/mysql -lmysql test_mysql.c