gcc編譯undefined reference to本質原因
專案中的LVS用到keepalived和ipvsadm等三方件,在suse11和suse12上編譯最新版本的過程中遇到的最多的錯誤便是 undefined reference to xxx。由於對背後的原理基本沒啥理解,所以遇到問題的解決辦法就是把錯誤資訊拿去google,baidu搜。當遇到的問題越來越偏僻時,這種做法不僅學不到多少東西,也無法快速的解決問題。所以下定決心從頭學起,剛好對此也非常感興趣,學習這些知識對學習作業系統一定非常有幫助。
動手實踐之前,先來一點理論知識的指導:
gcc程式的編譯過程和連結原理
為了加深印象與理解,按自己的理解總結一下,gcc的編譯選項:
- -o 指定輸出檔名為file,這個名稱不能跟原始檔名同名。不指定時,-E會輸出到標準輸出(一般為螢幕)。-S輸出到file.s。-c 輸出到file.o。不使用以上選項時,輸出到a.out。
- -E Preprocess only; do not compile, assemble or link;只預處理
- -S Compile only; do not assemble or link;只編譯(得到的是彙編程式碼,mov,push這種
- -c Compile and assemble, but do not link; 編譯和彙編。得到的是二進位制。我理解此時的二進位制已經和作業系統的具體指令相關了。
- 不指定引數,預設進行編譯,彙編,連結。得到是可執行二進位制。
瞭解了原理,接下來進行實踐加深理解。
參考:
Linux makefile – undefined reference to 問題解決方法
測試程式碼如下:
[email protected]:~/workspace/test1$ cat main.c
#include <stdio.h>
int main()
{
test();
}
[email protected]:~/workspace/test1$ cat test.c
#include <stdio.h>
void test()
{
printf("I am test!\n");
}
直接進行編譯:
[email protected]:~/workspace/test1$ gcc -o main main.c
main.c: In function ‘main’:
main.c:5:2: warning: implicit declaration of function ‘test’ [-Wimplicit-function-declaration]
test();
^~~~
/tmp/ccXDYxeL.o: In function `main':
main.c:(.text+0x1c): undefined reference to `test'
collect2: error: ld returned 1 exit status
加上test.c便正確:
copbint@debian2:~/workspace/test1$ gcc -o main main.c test.c
main.c: In function ‘main’:
main.c:5:2: warning: implicit declaration of function ‘test’ [-Wimplicit-function-declaration]
test();
^~~~
雖然能夠連結成功,但是還是會有警告。經過測試,原來是在編譯的過程中,發出了警告。
[email protected]:~/workspace/test1$ gcc -S main.c test.c
main.c: In function ‘main’:
main.c:5:2: warning: implicit declaration of function ‘test’ [-Wimplicit-function-declaration]
test();
^~~~
由此可以理解,在編譯的過程中,如果找不到函式的實現,只會丟擲implicit declaration of的警告。如果在連結的過程中,找不到函式的實現,則會導致錯誤。
在main.c中加入test()的宣告,再實驗如下:
[email protected]:~/workspace/test1$ cat main.c
#include <stdio.h>
void test();
int main()
{
test();
}
[email protected]:~/workspace/test1$ cat test.c
#include <stdio.h>
void test()
{
printf("I am test!\n");
}
[email protected]:~/workspace/test1$ gcc -c main.c
[email protected]:~/workspace/test1$ gcc -o main main.c
/tmp/ccISqKfs.o: In function `main':
main.c:(.text+0x1c): undefined reference to `test'
collect2: error: ld returned 1 exit status
由此可以得出結論:
undefined reference to xxx是由於gcc在連結的過程中找不到函式的實現而導致的錯誤。如果是找不到函式的宣告,會在編譯的過程出丟擲警告。
那麼,如果在程式碼中使用未定義的變數,在編譯(不連結)的過程中是否像未定義的函式一樣,僅僅是丟擲警告而不會導致錯誤呢?
[email protected]:~/workspace/test1$ cat main.c
#include <stdio.h>
int main()
{
test_h_var = 3;
}
[email protected]:~/workspace/test1$ gcc -c main.c
main.c: In function ‘main’:
main.c:5:2: error: ‘test_h_var’ undeclared (first use in this function)
test_h_var = 3;
^~~~~~~~~~
main.c:5:2: note: each undeclared identifier is reported only once for each function it appears in
在編譯的過程中,與使用未定義的函式不同,使用未定義的變數會直接導致錯誤。
在稍大一點的軟體專案中,如果用到一個函式,就手動的去宣告一次,會有工作量大且難以維護的問題。標頭檔案就是為了解決這個問題,如為test.c新增一個test.h。然後在main.c中引用。
[email protected]:~/workspace/test1$ cat main.c
#include <stdio.h>
#include "test.h"
int main()
{
test();
}
[email protected]:~/workspace/test1$ cat test.h
void test();
使用<>括起來的標頭檔案,gcc會去系統路徑下查詢(具體路徑還不瞭解),而”“括起來的標頭檔案,gcc會嘗試在當前目錄搜尋。
標頭檔案除了宣告函式,還有定義變數,巨集,結構體等的作用。所以,在實際應用過程中,標頭檔案必不可少。
在suse11上編譯keepalived1.3.5的過程中,遇到大量 undefined reference to xxx錯誤,但是卻並未看到implicit declaration of的告警資訊。說明是在連結的過程中未找到對應函式的實現體。那麼什麼原因會導致這種情況呢?
未完待續……