C語言archive 明明有symbol卻link時出現unsolved Symbol錯誤

說明

GNU ld 手冊提到,archive (可能是.a或是.o檔)在link時預設只會reference一次,如果沒有注意的話會發現明明有symbol卻link時出現unsolved symbol錯誤


測試

下面是測試程式碼liba.a包含的程式碼liba.a包含的程式碼,可以看到liba.a的function去呼叫libb的function,反之亦然。

#include "libb.h"
#include "liba.h"

int main(void)
{
    test_liba();
    test_libb();

    return 0;
}
#include "libb.h"
#include <stdio.h>

void test_liba(void)
{
    from_libb();
}

void from_liba()
{
    printf("%s\n", __PRETTY_FUNCTION__);
}
#include "liba.h"
#include <stdio.h>

void test_libb(void)
{
    from_liba();
}

void from_libb()
{
    printf("%s\n", __PRETTY_FUNCTION__);
}

而測試的Makefile如下,變數說明如下

  • $^: prerequisites
  • $@: target
  • $>: 第一個prerequisite
all: liba.a libb.a test.o
    gcc -o test $^

liba.a: liba.o liba.h
    ar crv $@ $<

libb.a: libb.o libb.h
    ar crv $@ $<

clean:
    rm -rf *.o *.a *~

編譯輸出如下,正如前面的預想

$ make
cc    -c -o liba.o liba.c
ar crv liba.a liba.o
a - liba.o
cc    -c -o libb.o libb.c
ar crv libb.a libb.o
a - libb.o
cc    -c -o test.o test.c
gcc -o test liba.a libb.a test.o
test.o: In function `main':
test.c:(.text+0x5): undefined reference to `test_liba'
test.c:(.text+0xa): undefined reference to `test_libb'
collect2: ld returned 1 exit status
make: *** [all] Error 1

解法

解法一:調整link順序

  • 將Makefile的all: liba.a libb.a test.o順序改成all: test.o liba.a libb.a可以正常編譯
    • 延伸測試
      • 將Makefile的all: liba.a libb.a test.o順序改成all: liba.a test.o libb.a會發生下面的錯誤
  • 關於link怎麼resovle symbol部份就不在本篇中討論,抱歉。
$ make
cc    -c -o liba.o liba.c
ar crv liba.a liba.o
a - liba.o
cc    -c -o test.o test.c
cc    -c -o libb.o libb.c
ar crv libb.a libb.o
a - libb.o
gcc -o test liba.a test.o libb.a
test.o: In function `main':
test.c:(.text+0x5): undefined reference to `test_liba'
libb.a(libb.o): In function `test_libb':
libb.c:(.text+0x5): undefined reference to `from_liba'
collect2: ld returned 1 exit status
make: *** [all] Error 1

解法二:使用--start-group ... --end-group參數

  • 將Makefile 的gcc -o test $^改成gcc -o test -Wl,--start-group $^ -Wl,--end-group
  • 參數說明如下

    • -Wl: 告訴gcc傳參數給linker
    • --start-group ... --end-group
      • 告訴linker在中間的...重複找尋symbol
      • 這樣的行為有效能代價,請謹慎使用
  • 編譯並執行結果如下

$ make
cc    -c -o liba.o liba.c
ar crv liba.a liba.o
a - liba.o
cc    -c -o libb.o libb.c
ar crv libb.a libb.o
a - libb.o
cc    -c -o test.o test.c
gcc -o test -Wl,--start-group liba.a libb.a test.o -Wl,--end-group

$ ./test
from_libb
from_liba

版本資訊

  • GNU ld 2.2

參考資料

  • man ld
    • 搜尋start-group