在《C 语言和 C++ 到底有什么关系?》一节中,我们了解到 C++ 在 C 的基础上扩展了面向对象和泛型编程,并引入了命名空间、函数重载、内联函数等特性。那么,能否在同一项目中同时使用 C++ 与 C 代码?答案是:可以,但需要处理二者在编译和链接时对符号的不同处理方式。
一、符号名称修饰(Name Mangling)差异
-
C 编译器
只保留原始函数名,符号表中就是 display 、swap 等简单名称。
-
C++ 编译器
为了支持函数重载,会将函数名和参数类型一起编码(名称修饰),例如:
void Swap(int,int) → _Z4Swapii
void Swap(float,float)→ _Z4Swapff
(不同编译器的修饰策略不尽相同,仅作示意)
若在 C++ 单元(.cpp )中调用纯 C 函数,由于名称修饰不一致,链接器将无法匹配符号,从而报 “undefined reference” 错误。
二、错误演示:省略 extern "C" 导致链接失败
myfun.h(无 extern "C" )
// myfun.h
void display();
myfun.c
#include <stdio.h>
#include "myfun.h"
void display() {
printf("Hello from C\n");
}
main.cpp
#include <iostream>
#include "myfun.h"
using namespace std;
int main() {
display();
return 0;
}
编译与链接命令
gcc -c myfun.c # 生成 myfun.o
g++ -c main.cpp # 生成 main.o
g++ myfun.o main.o -o mixed_app
典型错误输出
/usr/bin/ld: main.o: in function `main':
main.cpp:(.text+0x5): undefined reference to `display()'
collect2: error: ld returned 1 exit status
- C++ 编译器将
display() 在符号表中存为类似 _Z7displayv 。
- C 编译器导出的符号仍是纯粹的
display 。
- 链接器无法将二者对上号,因而报错。
三、解决方案:extern "C"
C++ 提供了 extern "C" 机制,指示编译器“按 C 的方式”处理符号。常见用法:
-
修饰单个函数声明
#ifdef __cplusplus
extern "C" void display();
#else
void display();
#endif
-
修饰一段代码块
#ifdef __cplusplus
extern "C" {
#endif
void display();
int compute(int, int);
// … 其他 C 接口 …
#ifdef __cplusplus
}
#endif
- 在 C++ 单元中,
__cplusplus 宏被定义,使用 extern "C" ,避免名称修饰。
- 在 C 单元中,
__cplusplus 未定义,恢复为普通 C 声明。
四、完整示例:混合编程成功案例
目录结构
project/
├── myfun.h
├── myfun.c
└── main.cpp
myfun.h
#ifdef __cplusplus
extern "C" {
#endif
void display();
#ifdef __cplusplus
}
#endif
myfun.c
#include <stdio.h>
#include "myfun.h"
void display() {
printf("Hello from C\n");
}
main.cpp
#include <iostream>
#include "myfun.h"
using namespace std;
int main() {
display(); // 链接到 myfun.c 中的 C 函数
return 0;
}
编译命令
gcc -c myfun.c
g++ -c main.cpp
g++ myfun.o main.o -o mixed_app
运行输出
C++:http://c.biancheng/net/cplus/
五、最佳实践
-
在头文件中使用条件宏 + extern "C"
确保同一接口既能被 C 编译器识别,也能被 C++ 编译器正确处理。
-
只在头文件中添加 extern "C"
实现文件(.c 或 .cpp )保持原样,避免冗余。
-
第三方 C 库集成
若对方头文件无 extern "C" ,可在包含时手动包裹:
extern "C" {
#include "third_party_c.h"
}
-
接口命名独立
C 接口尽量使用独特命名,减少与 C++ 标准库或其他模块冲突。
六、小结
- C 与 C++ 完全兼容混合编程,但名称修饰差异会导致链接问题。
- 通过
extern "C" ,可让 C++ 编译器按 C 方式导出/引用符号,解决混合链接失败。
- 在跨语言边界的头文件中,始终结合
#ifdef __cplusplus 与 extern "C" ,确保接口在 C/C++ 环境中都能正确编译与链接。
|