C++ 支援函數重載,編譯器將函數簽名 (參數類型、個數、命名空間、類別) 編碼進符號名稱 (Mangled Name),如 void foo(int, float) → _Z3fooif (GCC) 或 ?foo@@YAXHM@Z (MSVC)。
C 無重載,符號名通常僅加前綴底線 _foo。
若 C++ 直接調用 C 函數,連結器會尋找 C++ 風格符號而失敗。
[cpp]
// cExample.h (C 標頭檔)
#ifndef C_EXAMPLE_H
#define C_EXAMPLE_H
extern int add(int x, int y); // 僅 extern,不可加 extern "C"
#endif
// cExample.c
#include "cExample.h"
int add(int x, int y) { return x + y; }
// cppFile.cpp (C++ 使用)
extern "C" {
#include "cExample.h" // 整個標頭檔包在 extern "C" 區塊
}
int main() { add(2, 3); return 0; }
編譯鏈:gcc -c cExample.c -o cExample.o → g++ cppFile.cpp cExample.o -o cppFile。
[cpp]
// cppExample.h (C++ 標頭檔,對外暴露 C 介面)
#ifndef CPP_EXAMPLE_H
#define CPP_EXAMPLE_H
#ifdef __cplusplus
extern "C" {
#endif
int add(int x, int y); // C++ 實現,但用 C 連結
#ifdef __cplusplus
}
#endif
#endif
// cppExample.cpp
#include "cppExample.h"
int add(int x, int y) { return x + y; }
// cFile.c (C 使用)
// 錯誤:#include "cppExample.h" (C 編譯器不識別 extern "C")
// 正確:僅聲明
extern int add(int x, int y);
int main() { add(2, 3); return 0; }
在 C++ 文件包含 C 標頭檔時,務必加 extern "C" 區塊;若直接在 C++ 中定義需給 C 用的函數,定義處也需加:
[cpp]
// 定義在 .cpp 中,供 C 調用
extern "C" int my_c_func(int x) { return x * 2; }
Windows .dll / Linux .so 導出函數給不同語言調用,必須用 extern "C" 避免名稱修飾,否則 GetProcAddress/dlsym 需知具體修飾名極不便。
__cplusplus 守護宏使同一標頭檔同時適配 C 與 C++:
[c]
// mylib.h
#ifndef MYLIB_H
#define MYLIB_H
#ifdef __cplusplus
extern "C" {
#endif
int mylib_add(int a, int b);
void mylib_init(void);
#ifdef __cplusplus
}
#endif
#endif
| 現象 | 原因 | 修正 |
|---|---|---|
| C++ 編譯 .c 文件報錯 | g++ file.c 以 C++ 模式編譯 C 代碼 | gcc -c file.c 產 .o 再 g++ main.cpp file.o |
| 連結錯誤: undefined reference | 聲明有 extern "C" 但定義無,或反之 | 聲明與定義**連結規範必須一致** |
| C 文件包含含 extern "C" 的頭文件報錯 | C 編譯器不識別 extern "C" | 頭文件用 #ifdef __cplusplus 守護 |
| 類成員函數加 extern "C" | 非靜態成員函數有 this 指標,無法用 C 連結 | 僅靜態成員函數或自由函數可用 |
extern "C" 是 **C/C++ 混合編程、動態庫導出、跨語言調用 (Python ctypes, Java JNI, C# P/Invoke)** 的基石。核心口訣:聲明與定義處連結規範對齊,頭文件用 __cplusplus 守護,編譯分離 .c/.cpp 產 .o 再鏈接。