全局變量存儲歸屬規則(GCC 默認行為):
關鍵差異:強符號(定義並分配存儲)與弱符號(暫時佔位,鏈接時可被強符號覆蓋)。
// test1.c
int init = 0; // 強符號,放入 .bss
void init1() { if (0 == init) { init = 1; printf("init1\n"); } }
// test2.c
int init = 1; // 強符號,放入 .data
void init2() { if (init) { init = 0; printf("init2\n"); } }
鏈接報錯:multiple definition of 'init'。兩個強符號無法共存。
// test1.c 修改為
int init; // 未初始化 → COMMON 段(弱符號)
// test2.c 保持不變
int init = 1; // 強符號
鏈接成功,無警告(即使 -Wall)。運行結果僅輸出 init2。
原因:鏈接器用 test2.c 的強符號(值 1)覆蓋 test1.c 的弱符號。init1 讀到的初值為 1 而非預期的 0,邏輯失效。
| 文件 | 代碼 | 符號類型 | 段 | 初值 |
|---|---|---|---|---|
| test1.c | int init = 0; | 強符號 | .bss | 0 |
| test2.c | int init = 1; | 強符號 | .data | 1 |
| test1.c | int init; | 弱符號 | COMMON | 0(鏈接後) |
| test2.c | int init = 1; | 強符號 | .data | 1 |
鏈接規則:強符號覆蓋弱符號,多個強符號報錯。場景二中,弱符號被強符號「靜默覆蓋」,導致運行時邏輯錯誤且編譯器不報警。
static:限制作用域,避免符號暴露。int init = 0;,也能生成強符號,觸發鏈接錯誤而非靜默覆蓋。module_init_flag)。-fno-common:強制將未初始化全局變量直接放入 .bss(強符號),還原場景一行為,編譯期暴露衝突。gcc -fno-common -c test1.c test2.c
gcc -fno-common -o test main.c test1.o test2.o
# 重現 multiple definition 錯誤
BSS 與 COMMON 的區別不在於運行時值(均為 0),而在於符號強弱屬性。未初始化全局變量產生弱符號,極易被同名強符號靜默覆蓋,導致難以排查的邏輯錯誤。習慣顯式初始化、善用 static、開啟 -fno-common 是 C 語言工程化的必修課。