在研讀《程序員的自我修養——鏈接、裝載與庫》第 3.5.5 節時,我發現自己對 C 語言中的「強符號、弱符號、強引用、弱引用」一無所知。這幾個概念乍看之下容易混淆,但對於理解鏈接器的行為、避免多重定義錯誤、實現函數覆蓋與插件化架構卻至關重要。本文將結合書籍定義、GCC 實際行為與代碼實驗,系統梳理這些概念,期待與同好交流、專家指正。
根據書中定義:
/* a.c */ int i = 2; // 強符號(已初始化)
/* b.c */
#include <stdio.h>
int i; // 弱符號(未初始化)
int main() { printf("i = %d\n", i); return 0; }
編譯鏈接後輸出 i = 2,而非 0。證明強符號 a.c 的定義覆蓋了弱符號 b.c 的聲明。
GCC 允許將強符號強制標記為弱符號:
__attribute__((weak)) int i = 1; // 原本是強符號,現在變弱符號
若與另一個強符號定義(如 int i = 2;)鏈接,強符號依然勝出,輸出 2。
書籍將「引用」定義為:在定義/聲明處的名稱為符號,在調用/使用處的名稱為引用。兩者本質相同,因上下文而異名。
/* d.c - 提供實現 */
void func() { printf("func()#1\n"); }
/* e.c - 弱引用,可選調用 */
__attribute__((weak)) void func();
int main() { if (func) func(); return 0; }
d.o:正常執行 func()。d.o:鏈接成功,if (func) 為假,func() 不執行,程序正常退出。/* f.c - 庫提供預設實現(弱符號) */
__attribute__((weak)) void func() { printf("func()#1\n"); }
/* g.c - 用戶提供強符號覆蓋 */
void func() { printf("func()#2\n"); }
int main() { func(); return 0; }
輸出 func()#2。這是實現「庫提供預設,用戶可覆蓋」的核心機制。
/* v1.c */ int i = 2;
/* v2.c */
__attribute__((weak)) extern int i; // 將外部強符號轉為弱引用
int main() { if (&i) printf("i = %d\n", i); return 0; }
v1.o:輸出 2。v1.o:&i == 0,無輸出。關鍵區別:`__attribute__((weak)) int i` 是定義弱符號(分配空間);`__attribute__((weak)) extern int i` 是聲明弱引用(不分配空間,僅改變引用屬性)。
`weakref` 更貼近「引用」語義,允許指定備用符號:
/* a.c */ void bar() { printf("foo()\n"); }
/* b.c */
static void foo() __attribute__((weakref("bar")));
int main() { if (foo) foo(); return 0; }
static 限制 foo 僅在本文件可見,避免符號衝突。bar 存在,foo 指向 bar;否則 foo 為 0。強弱符號/引用機制是鏈接器解決符號衝突、支持模組化設計的基石。掌握它們能幫助我們:
文中如有疏漏或錯誤,懇請各位大神不吝指正,共同進步。