在 C++ 專案整合 C 庫、作業系統 API 或組合語言模組時,extern "C" 是不可繞開的關鍵字。它解決的核心問題是名稱修飾差異:C++ 為支援函式多載,編譯器會將函式簽名編碼進符號名(Name Mangling);而 C 語言符號僅保留函式名稱。若不加干預,連結器將在目標檔中找不到對應符號,導致「未定義的引用」錯誤。
extern 宣告變數或函式擁有外部鏈接,可跨編譯單元(.c/.cpp 檔)引用。關鍵細節:
extern 僅為聲明而非定義extern,可省略extern int g_var; 僅在該區塊可見,不影響鏈接屬性extern "C" 指示編譯器:以 C 語言規則生成符號名、呼叫慣例與參數傳遞方式。具體影響:
| 項目 | C 編譯 | C++ 編譯(無 extern "C") | C++ 編譯(有 extern "C") |
|---|---|---|---|
函式 void foo(int, float) | _foo | _foo_int_float(Mangled) | _foo |
全域變數 int g_val | _g_val | _g_val(通常同 C) | _g_val |
| 呼叫慣例 | cdecl | cdecl / thiscall 等 | cdecl |
// C 標頭檔 c_lib.h
#ifndef C_LIB_H
#define C_LIB_H
#ifdef __cplusplus
extern "C" {
#endif
int c_add(int a, int b);
#ifdef __cplusplus
}
#endif
#endif
// C++ 使用端
#include "c_lib.h"
int main() { return c_add(2, 3); }
關鍵技巧:標頭檔內部以 #ifdef __cplusplus 守護,單一標頭檔同時支援 C/C++ 引入,避免維護雙份介面。
// C++ 實作 cpp_lib.cpp
extern "C" int cpp_mul(int a, int b) { return a * b; }
// C 使用端 c_main.c
extern int cpp_mul(int, int); // 僅需 extern,不可寫 extern "C"
int main() { return cpp_mul(4, 5); }
C 編譯器不識別 extern "C",故 C 端僅以 extern 宣告即可;C++ 端定義時必須加 extern "C" 導出 C 風格符號。
Windows DLL、Linux .so 導出函式若需給 C 語言呼叫,必須標記 extern "C",否則 GetProcAddress/dlsym 將因符號名不符而失敗。
C 標準庫 qsort、signal、pthread_create 等接受函式指標的 API,回調函式必須具備 C 鏈接,否則 C++ 靜態成員函式或 Lambda(無捕獲)雖可隱式轉換,但符號不匹配會導致運行時崩潰。
#ifdef __cplusplus 包裹 extern "C",而非要求調用方在 #include 外層包裹。extern "C" 僅適用於非成員函式;需轉發至靜態成員或自由函式。extern "C" 是 C/C++ 混合編程的基石,本質上是告訴 C++ 編譯器「這裡請切換到 C 模式」。理解其背後的名稱修飾機制、呼叫慣例差異與鏈接模型,能讓你在跨語言專案、動態庫開發、嵌入式系統整合中遊刃有餘,避免 90% 的連結階段疑難雜症。