連結指示 extern "C"
2008年9月7日 星期日 by Anati
什麼叫做連結指示呢?一般來說,這是給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
※ 連結的運作
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
您好:
如你文章所提~當要使用其它編譯器編輯後的函式,則要使用連結指示.....
不過我遇到的問題是~..
來源碼都是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