undefined reference to 'xxx'
出現問題的原因是c庫函數編譯成obj文件時對函數符號的處理和C++不同。因爲C++函數支持重載,所以函數符號的處理要更復雜一些,c往往不作修飾。
例如有函數:
/* dofunc.c */
#include <stdio.h>
int dofunc()
{
printf("dofunc\n");
}
#include <stdio.h>
int dofunc()
{
printf("dofunc\n");
}
使用gcc編譯成obj後
gcc -c dofunc.c
#生成 dofunc.o
objdump -x dofunc.o
[ 0](sec -2)(fl 0x00)(ty 0)(scl 103) (nx 1) 0x00000000 dofunc.c
File
[ 2](sec 1)(fl 0x00)(ty 20)(scl 2) (nx 1) 0x00000000 _dofunc
AUX tagndx 0 ttlsiz 0x0 lnnos 0 next 0
[ 4](sec 1)(fl 0x00)(ty 0)(scl 3) (nx 1) 0x00000000 .text
AUX scnlen 0x14 nreloc 2 nlnno 0
[ 6](sec 2)(fl 0x00)(ty 0)(scl 3) (nx 1) 0x00000000 .data
AUX scnlen 0x0 nreloc 0 nlnno 0
[ 8](sec 3)(fl 0x00)(ty 0)(scl 3) (nx 1) 0x00000000 .bss
AUX scnlen 0x0 nreloc 0 nlnno 0
[ 10](sec 4)(fl 0x00)(ty 0)(scl 3) (nx 1) 0x00000000 .rdata
AUX scnlen 0x8 nreloc 0 nlnno 0
[ 12](sec 0)(fl 0x00)(ty 20)(scl 2) (nx 0) 0x00000000 _printf
#生成 dofunc.o
objdump -x dofunc.o
[ 0](sec -2)(fl 0x00)(ty 0)(scl 103) (nx 1) 0x00000000 dofunc.c
File
[ 2](sec 1)(fl 0x00)(ty 20)(scl 2) (nx 1) 0x00000000 _dofunc
AUX tagndx 0 ttlsiz 0x0 lnnos 0 next 0
[ 4](sec 1)(fl 0x00)(ty 0)(scl 3) (nx 1) 0x00000000 .text
AUX scnlen 0x14 nreloc 2 nlnno 0
[ 6](sec 2)(fl 0x00)(ty 0)(scl 3) (nx 1) 0x00000000 .data
AUX scnlen 0x0 nreloc 0 nlnno 0
[ 8](sec 3)(fl 0x00)(ty 0)(scl 3) (nx 1) 0x00000000 .bss
AUX scnlen 0x0 nreloc 0 nlnno 0
[ 10](sec 4)(fl 0x00)(ty 0)(scl 3) (nx 1) 0x00000000 .rdata
AUX scnlen 0x8 nreloc 0 nlnno 0
[ 12](sec 0)(fl 0x00)(ty 20)(scl 2) (nx 0) 0x00000000 _printf
c的dofunc函數在obj文件裏的符號爲 _dofunc
再看看使用g++編譯後的代碼:
g++ -c dofunc.c
objdump -x dofunc.o
SYMBOL TABLE:
[ 0](sec -2)(fl 0x00)(ty 0)(scl 103) (nx 1) 0x00000000 dofunc.c
File
[ 2](sec 1)(fl 0x00)(ty 20)(scl 2) (nx 1) 0x00000000 __Z6dofuncv
AUX tagndx 0 ttlsiz 0x0 lnnos 0 next 0
[ 4](sec 1)(fl 0x00)(ty 0)(scl 3) (nx 1) 0x00000000 .text
AUX scnlen 0x14 nreloc 2 nlnno 0
[ 6](sec 2)(fl 0x00)(ty 0)(scl 3) (nx 1) 0x00000000 .data
AUX scnlen 0x0 nreloc 0 nlnno 0
[ 8](sec 3)(fl 0x00)(ty 0)(scl 3) (nx 1) 0x00000000 .bss
AUX scnlen 0x0 nreloc 0 nlnno 0
[ 10](sec 4)(fl 0x00)(ty 0)(scl 3) (nx 1) 0x00000000 .rdata
AUX scnlen 0x8 nreloc 0 nlnno 0
[ 12](sec 0)(fl 0x00)(ty 20)(scl 2) (nx 0) 0x00000000 _printf
objdump -x dofunc.o
SYMBOL TABLE:
[ 0](sec -2)(fl 0x00)(ty 0)(scl 103) (nx 1) 0x00000000 dofunc.c
File
[ 2](sec 1)(fl 0x00)(ty 20)(scl 2) (nx 1) 0x00000000 __Z6dofuncv
AUX tagndx 0 ttlsiz 0x0 lnnos 0 next 0
[ 4](sec 1)(fl 0x00)(ty 0)(scl 3) (nx 1) 0x00000000 .text
AUX scnlen 0x14 nreloc 2 nlnno 0
[ 6](sec 2)(fl 0x00)(ty 0)(scl 3) (nx 1) 0x00000000 .data
AUX scnlen 0x0 nreloc 0 nlnno 0
[ 8](sec 3)(fl 0x00)(ty 0)(scl 3) (nx 1) 0x00000000 .bss
AUX scnlen 0x0 nreloc 0 nlnno 0
[ 10](sec 4)(fl 0x00)(ty 0)(scl 3) (nx 1) 0x00000000 .rdata
AUX scnlen 0x8 nreloc 0 nlnno 0
[ 12](sec 0)(fl 0x00)(ty 20)(scl 2) (nx 0) 0x00000000 _printf
g++編譯後的函數符號名比較古怪:__Z6dofuncv
可見C和C++在加工函數名方面是很大不同的。
如果有C++程序要使用dofunc.o ,如下程序的函數聲明是錯的
//
main_dev.cpp
int dofunc();
int main(int argc , char* args[])
{
dofunc();
system("pause");
}
g++ -o main_dev main_dev.cpp dofunc.o
main_dev.cpp: undefined reference to `dofunc()'
collect2: ld returned 1 exit status
int dofunc();
int main(int argc , char* args[])
{
dofunc();
system("pause");
}
g++ -o main_dev main_dev.cpp dofunc.o
main_dev.cpp: undefined reference to `dofunc()'
collect2: ld returned 1 exit status
原因是dofunc函數在加工後函數名應該爲__Z6dofuncv ,dofunc.o文件裏面的是_dofunc,所以找不到。
如果有dofunc的源代碼,解決辦法很簡單,將dofunc.c使用c++來編譯即可。
如果不幸地dofunc函數在別人的庫裏面,而這個庫是用c編寫和gcc編譯的,源代碼不可見,那怎麼辦呢?
幸虧C++和編譯器的設計者早已料到了這個問題,並提供了一種通用的解決辦法:使用extern "C"來修飾舊C庫的外部函數聲明。
extern "C" {
int dofunc();
}
int main(int argc , char* args[])
{
dofunc();
system("pause");
}
g++ -o main_dev main_dev.cpp dofunc.o
成功
int dofunc();
}
int main(int argc , char* args[])
{
dofunc();
system("pause");
}
g++ -o main_dev main_dev.cpp dofunc.o
成功
extern "C"修飾內的函數,一律按照c的風格來編譯,以便能夠鏈接到用c編譯出來的obj庫上去。
常見有形如:
#ifdef __cplusplus
extern "C" {
#endif
int dofunc();
#ifdef __cplusplus
}
#endif
extern "C" {
#endif
int dofunc();
#ifdef __cplusplus
}
#endif
的頭文件聲明。
這種的頭文件一般是庫開發者提供的,能同時被c和c++模塊使用。宏__cplusplus 是c++編譯器定義的,這種寫法保證了用C++編譯時extern "C" 能生效;而用c編譯時又不會因不會處理extern "C"而錯誤。
反過來,如果c需要調用C++編譯的庫又怎麼辦呢?相信一般情況下不會有這樣奇特的要求,直接用C++編譯不就完了?
把main_dev.cpp改名爲main.c ,然後
gcc -o main_dev main_dev.c dofunc.o
當然會出現: undefined reference to `dofunc'
因爲fofunc.o裏面的符號是__Z6dofuncv ,所以鏈接會失敗,只能有一種非常噁心的方法去鏈到那個函數:
//main_dev.c
int (*dofunc)(); /* 聲明函數指針 */
int _Z6dofuncv(); /* 會鏈接到 __Z6dofuncv */
int main(int argc , char* args[])
{
dofunc=_Z6dofuncv; /* 函數指針賦值 */
dofunc();
system("pause");
}
gcc -o main_dev main_dev.c dofunc.o
成功
int (*dofunc)(); /* 聲明函數指針 */
int _Z6dofuncv(); /* 會鏈接到 __Z6dofuncv */
int main(int argc , char* args[])
{
dofunc=_Z6dofuncv; /* 函數指針賦值 */
dofunc();
system("pause");
}
gcc -o main_dev main_dev.c dofunc.o
成功
上面講了那麼多,中心意思都是c和c++編譯和鏈接時對函數名加工的細節問題,理解了這些細節後,如何運用完全就存乎一心了。
以上淺見,歡迎指正。
本文出自 “軟件工匠筆記” 博客,請務必保留此出處http://linhs.blog.51cto.com/370259/140927