1. 程式人生 > >gcc編譯undefined reference to本質原因

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的告警資訊。說明是在連結的過程中未找到對應函式的實現體。那麼什麼原因會導致這種情況呢?

未完待續……