解決 Linux 上 C/C++ 的 undefined symbol 或 undefined reference
使用 C/C++ 程式分成三個步驟: 編譯 (compile) → 連結 (link) → 執行 (載入 symbol)。了解每個階段的行為,才知道如何處理該階段的 undefined symbol。
基本概念
- 編譯 .c / .cpp 為 .o (object file) 時,需要提供 header 檔。header 不在預設搜尋路徑時,可用
-I DIR
增加搜尋目錄。事實上,在編譯單一檔案時,gcc/g++ 不在意 symbol 是否存在,有宣告它就信了,所以關鍵是有引入正確的 header。這也是可分散編譯的原因 (如 distcc ),因為程式碼編譯成 .o 檔時,沒有相依性。 - 用 static linker (ld.bfd、gold、lld) 將 *.o 連結成 dynamic library 或執行檔時,通常需要確認找得到所有的 symbol,需要提供要連結的 library。用
-L DIR
指定目錄位置,用-l
指定函式庫名稱。 - 執行的時候,dynamic linker (又稱 runtime linker) 會載入 shared library 讀出 symbol。前一個步驟只是檢查是否有 symbol。檢查通過也連結成 executable 或 shared library 後,若執行時路徑不對,或檔案不見了,仍會在執行期找不到 symbol。
常用工具
- 查看 BINARY 內含有或缺少的 symbol
用 nm -D BINARY
列出 BINARY (object file、static/dynamic library、executable) dynamic section 的 symbol:
- T/t 分別表示 global/local symbol。
- U 表示 undefined symbol。
用 nm -Du BINARY
只會顯示使用到但未定義的 symbol。
2. 用 pkg-config 列出編譯/連結用的參數
系統內安全的開發套件,可用 pkg-config --cflags LIB
列出編譯時使用 LIB 需要的參數,pkg-config --libs LIB
列出連結時使用的參數。
以 Gtk+ 為例,安裝開發套件後,用 dpkg -L
找出 pkg-config 的設定檔:
$ dpkg -L libgtk-3-dev:amd64 | grep "pc$"
/usr/lib/x86_64-linux-gnu/pkgconfig/gtk+-unix-print-3.0.pc
/usr/lib/x86_64-linux-gnu/pkgconfig/gdk-x11-3.0.pc
/usr/lib/x86_64-linux-gnu/pkgconfig/gdk-broadway-3.0.pc
/usr/lib/x86_64-linux-gnu/pkgconfig/gdk-wayland-3.0.pc
/usr/lib/x86_64-linux-gnu/pkgconfig/gtk+-broadway-3.0.pc
/usr/lib/x86_64-linux-gnu/pkgconfig/gtk+-wayland-3.0.pc
/usr/lib/x86_64-linux-gnu/pkgconfig/gtk+-mir-3.0.pc
/usr/lib/x86_64-linux-gnu/pkgconfig/gdk-mir-3.0.pc
/usr/lib/x86_64-linux-gnu/pkgconfig/gtk+-3.0.pc
/usr/lib/x86_64-linux-gnu/pkgconfig/gdk-3.0.pc
/usr/lib/x86_64-linux-gnu/pkgconfig/gtk+-x11-3.0.pc
想使用 Gtk+-3.0 的話,列出編譯時用到的參數:
$ pkg-config --cflags gtk+-3.0
-pthread -I/usr/include/gtk-3.0 -I/usr/include/at-spi2-atk/2.0 -I/usr/include/at-spi-2.0 -I/usr/include/dbus-1.0 -I/usr/lib/x86_64-linux-gnu/dbus-1.0/include -I/usr/include/gtk-3.0 -I/usr/include/gio-unix-2.0/ -I/usr/include/mirclient -I/usr/include/mircore -I/usr/include/mircookie -I/usr/include/cairo -I/usr/include/pango-1.0 -I/usr/include/harfbuzz -I/usr/include/pango-1.0 -I/usr/include/atk-1.0 -I/usr/include/cairo -I/usr/include/pixman-1 -I/usr/include/freetype2 -I/usr/include/libpng12 -I/usr/include/gdk-pixbuf-2.0 -I/usr/include/libpng12 -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include
列出連結時用到的參數:
$ pkg-config --libs gtk+-3.0
-lgtk-3 -lgdk-3 -lpangocairo-1.0 -lpango-1.0 -latk-1.0 -lcairo-gobject -lcairo -lgdk_pixbuf-2.0 -lgio-2.0 -lgobject-2.0 -lglib-2.0
解決編譯原始碼的問題 (compiler 的工作)
若使用的是系統裝的函式庫 X,裝上 X-dev 後會有 header,然後看有沒有帶 pkg-config
的設定檔。有的話用 pkg-config
可以得知該下什麼參數找到 header。沒有的話,用 dpkg -L X-dev | grep include
找出 header 位置。若是另外下載的程式碼,在裡面找找 header 也沒什麼困難。
下面列一些相關的資料。
1. 若是網路上看到別人的範例,裡面有正確的 include 位置,可以用 header 位置找出需要安裝的套件,像是使用 GDK event 要 include “gdk/gdkevents.h”:
$ apt-file search gdk/gdkevents.h
libgtk-3-dev: /usr/include/gtk-3.0/gdk/gdkevents.h
libgtk2.0-dev: /usr/include/gtk-2.0/gdk/gdkevents.h
2. 若使用的函式有附 man page,man page 可能會提到需要的編譯/連結參數。以 man 3 sqrt
為例:
SQRT(3) Linux Programmer's Manual SQRT(3)NAME
sqrt, sqrtf, sqrtl - square root functionSYNOPSIS
#include <math.h>double sqrt(double x);
float sqrtf(float x);
long double sqrtl(long double x);Link with -lm.Feature Test Macro Requirements for glibc (see fea‐
ture_test_macros(7)):sqrtf(), sqrtl():
_BSD_SOURCE || _SVID_SOURCE || _XOPEN_SOURCE >= 600
|| _ISOC99_SOURCE || _POSIX_C_SOURCE >= 200112L;
or cc -std=c99
#include
表示需要的 header。- 接著是函式的 signature。
- Link with -lm 表示使用編譯時要加
-lm
,linker 才會找到 libm.so。 - Feature Test Macro 是 UNIX 跨平台用的一套規範,見 man feature_test_macros 了解作用,以及 glibc 認得的類型。feature test macro 要定義在檔案的開頭或直接用
gcc -D
定義。
3. 可用 gcc -E
列出前置處理 (cpp) 展開 #include
、#define
、#if …
等指令後的程式碼,查看是否有引入正確的檔案。細節說明見《gcc 展開前置處理的技巧》。
解決連結 shared library / executable 的問題 (static linker 的工作)
類似處理編譯的作法,用系統附的套件 X 時,先看有沒有 pkg-config
的設定檔。沒有的話用 dpkg -L X-dev
找位置。
要注意的是 static library 和 shared library 行為不同。static library 只是一群 object file 的集合。它沒有記額外資訊 (像是需要那些 library)。若執行檔 X 用到 static library A,而 A 用到 library B。編 X 時,要加上 -lA
和 -lB
的參數。編 X 的部份要知道它用到的 static library 有那些相依性, 而不是 A 自己會搞定自己的相依性。若 A 是 shared library,在編出 A 的時候,會記錄 A 需要 B,編 X 時不需另外指定 -lB
。
更多細節見 《Linux 編譯 shared library 的方法和注意事項》。
解決執行期的問題 (dynamic / runtime linker 的工作)
可在執行時設環境變數 LD_DEBUG=libs,symbols
然後執行程式,了解載入的過程。除錯的細節見《Linux 執行時尋找 symbol 的流程以及 shared library 相關知識》。