首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C 語言頭文件中定義變數的陷阱:重複符號錯誤與正確做法

     編輯:關於C語言

前言

在頭文件 (`.h`) 中定義 (定義 = 分配儲存空間) 全域變數,會導致多檔案包含時出現連結器錯誤 `duplicate symbol`。本文解析原因、展示預處理過程、說明 `include guard` 為何失效,並給出正確的「宣告/定義分離」模式。

問題重現

/* t1.h */
#ifndef T1_H
#define T1_H
int a = 0;  /* 定義!分配儲存空間 */
#endif

/* t1.c */ #include "t1.h" #include "t2.h" int main(){ return 0; }
/* t2.h */ #include "t1.h"
/* t2.c */ #include "t2.h"

編譯 `t1.c`、`t2.c` 各產生 `.o`,連結時發現兩個 `a` 定義 → `ld: duplicate symbol`。

為何 #ifndef 失效?

`#ifndef` 僅在單一編譯單元 (translation unit = 一個 `.c` + 其包含的所有 `.h`) 內防止重複展開。
`t1.c` 編譯單元展開一次 `int a = 0;` → `t1.o` 含定義。
`t2.c` 編譯單元獨立展開一次 `int a = 0;` → `t2.o` 含定義。
連結器合併兩 `.o` 時發現兩個同名定義 → 錯誤。

預處理輸出驗證

$ gcc -E t1.c
...
int a = 0;   /* 來自 t1.h */

$ gcc -E t2.c
...
int a = 0;   /* 來自 t1.h → t2.h → t2.c */

兩個編譯單元各自完整展開了定義。

正確做法:宣告/定義分離

頭文件僅宣告

/* globals.h */
#ifndef GLOBALS_H
#define GLOBALS_H
extern int g_counter;  /* 宣告:告知編譯器型別與名稱,不分配空間 */
extern const char *g_version;
#endif

單一來源檔定義

/* globals.c */
#include "globals.h"
int g_counter = 0;           /* 定義:分配空間、初始化 */
const char *g_version = "1.0";

其他檔案包含頭文件即可使用

/* other.c */
#include "globals.h"
void foo() { g_counter++; }

特殊情況:頭文件必須定義時

若專案架構強制要求頭文件定義 (如某些嵌入式專案),可採用:

  • static:每編譯單元擁有獨立副本 (不共享),適合檔案內部常數。
  • inline (C99) / `static inline`:函數定義在頭文件,避免重複符號。
  • 宏控制:
    #ifdef DEFINE_GLOBALS
    #define EXTERN
    #else
    #define EXTERN extern
    #endif
    EXTERN int global1;
    僅在一個 `.c` 定義 `DEFINE_GLOBALS` 再包含此頭文件。

總結

頭文件只放宣告 (`extern`)、定義放 `.c`** 是 C 語言模組化的鐵律。理解「編譯單元獨立性」與「連結器合併符號」機制,即可徹底避開 `duplicate symbol` 坑。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved