C++ 竟然看不懂 C 代码?揭秘背后不为人知的真相!

开发
我们有一位神通广大的外交官 extern "C" ,它不仅精通双方的"方言",还能让这对欢喜冤家顺利牵手,在项目中和谐共处。

想象一下,C++ 和 C 这对编程语言界的欢喜冤家,就像是来自不同星球的外星人,虽然都在用代码交流,但总是鸡同鸭讲。别担心!我们有一位神通广大的外交官 extern "C" ,它不仅精通双方的"方言",还能让这对欢喜冤家顺利牵手,在项目中和谐共处!

来看个有趣的小例子

想象一下,我们有一个超级简单的 C 语言文件,它就像是一个害羞的小朋友,只会做两件事:加法和打招呼

// hello.h - 这是我们害羞的小朋友的自我介绍卡片 📝
int add(int a, int b);          // 会做加法的小能手 ➕
void print_hello(void);         // 会说"你好"的小可爱 👋
// hello.c - 这是小朋友展示才艺的舞台 🎪
#include "hello.h"

int add(int a, int b) {         // 1+1=2,就是这么简单! 🧮
    return a + b;
}

void print_hello(void) {        // 挥挥小手说你好 🌟
    printf("Hello from C!\n");
}

这个小朋友看起来很简单吧?但是当它想和 C++ 这个"大哥哥"玩耍的时候,却总是会遇到一些小麻烦。别着急,接下来我们就来看看如何让他们变成好朋友!🤝✨

哎呀,出问题啦!

当我们天真地想让 C++ 直接调用 C 的函数时,编译器就开始闹脾气了 😵:

// main.cpp - C++文件
#include "hello.h"

int main() {
    add(1, 2);      // 编译器:这是啥?没见过!🤔
    print_hello();   // 编译器:完全不认识啊!😱
}

为啥会这样呢?

原来啊,C++ 这个小机灵鬼为了支持函数重载这个炫酷功能 ✨,会给每个函数起个独特的"花名",这个过程叫做"名字修饰"(Name Mangling) 🎭。

就像给每个人起外号一样!比如:

  • 把add(int, int) 悄悄改名叫_Z3addii 🏷️
  • 把add(float, float) 改名叫_Z3addff 🎯
  • 把add(string, string) 改名叫_Z3addSsSs 📝

而我们的 C 语言就像个耿直boy,叫add 就是add,从不玩花样 🤪。

这就好比:

  • C语言的世界:小明就叫"小明" 👦
  • C++的世界:非要叫他"住在三楼打篮球特别溜还会弹吉他的小明" 🏀🎸

这样一来:

  • C++编译器看到_Z3addii 就知道:"啊,这是两个整数相加的函数" 🧮
  • C编译器看到这个名字就懵了:"这是啥外星文?" 👽

所以当 C++ 想调用 C 函数时,就会找不到对应的函数名,因为它在找带着花名的版本,而 C 那边只有朴实无华的原名 😅。这不就闹别扭了嘛~ 🎭

举个实际的例子

// C++ 代码
void print(int x) { }           // 编译后变成: _Z5printi
void print(double x) { }        // 编译后变成: _Z5printd
void print(char* x) { }         // 编译后变成: _Z5printPc

// C 代码
void print(int x) { }           // 编译后还是: print

这就是为什么我们需要 extern "C" 这个"翻译官" 🗣️,它能告诉 C++ 编译器: "嘿,这个函数不要给它起花名了,就用原名吧!" 🤝

解决方案

要解决这个问题,我们需要使用 extern "C" 来告诉 C++ 编译器:"嘿,这些函数是 C 语言的,请用 C 的方式处理!" 🗣️

正确的做法是这样的:

// hello.h - 改良版 🛠️
#ifdef __cplusplus  // 判断是否是C++编译器 🔍
extern "C" {        // 告诉C++编译器:里面的东西用C的规则处理 📜
#endif

int add(int a, int b);          // 加法函数 ➕
void print_hello(void);         // 打招呼函数 👋

#ifdef __cplusplus
}
// main.cpp - C++文件 🚀
#include "hello.h"

int main() {
    int result = add(1, 2);     // 现在可以快乐地调用啦! 😊✨
    print_hello();              // 完美运行~ 🎉🎈
    return 0;                   // 程序结束,返回0 🏁
}

深入理解 extern "C" 的使用场景

1. 在 C++ 中调用 C 函数库

很多优秀的底层库都是用 C 语言编写的 🏗️,比如 SQLite 💾、OpenSSL 🔒 等。要在 C++ 项目中使用这些库,就需要 extern "C" 🔗:

// 使用 OpenSSL 的例子 🔐
extern "C" {    // 打开 C 语言的大门 🚪
    #include <openssl/ssl.h>    // 引入加密模块 🗝️
    #include <openssl/err.h>    // 引入错误处理 ⚠️
}

// 现在可以开心地使用 OpenSSL 的函数啦~ 🎉 ✨ 🚀

2. 制作跨语言的动态链接库

如果你要制作一个既能被 C 又能被 C++ 调用的动态链接库,extern "C" 是必不可少的 🎯:

// mylib.h 📦
#ifdef __cplusplus
extern "C" {  // 打开魔法门 ✨
#endif

// 这些函数可以被 C/C++ 同时调用 🤝
__declspec(dllexport) int calculate(int x, int y);      // 计算功能 🧮
__declspec(dllexport) void process_data(const char* data);  // 数据处理 📊

#ifdef __cplusplus
}  // 关闭魔法门 🔮
#endif

3. 处理函数指针

在涉及回调函数时,extern "C" 特别重要:

// 错误示范 ❌
typedef void (*Callback)(int);  // C++ 风格的函数指针

// 正确示范 ✅
extern "C" {
    typedef void (*Callback)(int);  // 可以在 C/C++ 间通用的函数指针
}

注意事项 - 写好代码的小锦囊

  • 不支持重载 - C语言的单纯世界:
extern "C" {
    void print(int x);     // 小可爱,这样写没问题哦~ ✅ 🎉 
    void print(double x);  // 哎呀!C语言可不认识重载这个高级货 ❌ 💥 
    // C语言表示:我只想要一个print,不要整那么多花样!🙈
}
  • 类成员函数不能用 extern "C" - C++独有的小秘密:
class MyClass {
    extern "C" void method();  // 这样写编译器会生气的!❌ 💔 
    // C语言:类是啥?不认识!我只认识普通函数!🤔
};
  • 头文件保护 - 安全帽要戴好:
// 推荐的头文件保护方式 - 让代码穿上安全盔甲 📝 ✨ 
#ifndef MY_HEADER_H    // 打开保护罩 🎪
#define MY_HEADER_H    // 设置结界 ⭐

#ifdef __cplusplus     // 优雅地询问:这是C++编译器吗?🤔
extern "C" {           // 是的话,请用C的方式理解下面的代码 🔓
#endif

// 你的精彩代码在这里闪耀... ✨ 📄 ✍️
// 可以放心大胆地写声明啦!🌟

#ifdef __cplusplus
}                      // 礼貌地说再见 🔐
#endif

#endif // MY_HEADER_H  // 关闭结界 🌈
  • 命名冲突的处理 - 给代码起个好名字:
// 不好的做法 - 容易撞名字 ❌
extern "C" {
    void init();    // 这名字太常见啦!很容易撞车的 😱
}

// 好的做法 - 加个独特的前缀 ✅
extern "C" {
    void mylib_init();    // 这样就不怕和别人的init撞车啦 🚗 ✨
}
  • 混合编译的小技巧 - 让代码更灵活:
// 聪明的条件编译 🧠
#if defined(__cplusplus) && defined(_WIN32)
extern "C" {
    __declspec(dllexport) void smart_function(); // Windows下的导出函数 🪟
}
#elif defined(__cplusplus) && defined(__linux__)
extern "C" {
    __attribute__((visibility("default"))) void smart_function(); // Linux下的导出函数 🐧
}
#endif

实用小贴士 - 进阶使用指南

  • 记得给所有 extern "C" 函数写好文档注释
  • 避免在 extern "C" 函数中使用 C++ 特有的特性
  • 如果可能,尽量把 C 接口封装成 C++ 类
  • 定期检查跨语言接口的兼容性

💡 小提示:把 extern "C" 的声明集中管理在一个专门的头文件中,这样维护起来更方便!

责任编辑:赵宁宁 来源: everystep
相关推荐

2025-01-07 07:20:00

C++代码开发

2011-11-08 13:41:27

苹果siri人工智能数据中心

2014-11-06 10:35:57

程序员

2010-08-05 11:14:12

Flex优势

2011-04-29 10:47:18

虚拟化

2010-09-03 08:52:38

CSS

2018-06-01 11:21:49

软件开发真相

2019-06-05 12:49:07

云办公

2020-02-20 12:02:32

Python数据函数

2013-08-09 09:27:08

vCentervSphere

2010-04-19 16:09:22

Oracle控制文件

2013-07-16 13:59:15

空姐事件移动市场华强北生态链

2014-08-18 10:44:31

斯诺登

2011-11-15 10:25:56

IBMWindows

2012-11-30 14:13:01

2011-11-14 10:06:16

IBM大型机支持Windows系统POWER7

2021-02-05 09:58:52

程序员Windows系统

2010-09-06 14:19:54

CSS

2017-03-28 08:40:14

2011-10-19 16:19:27

iOS 5苹果
点赞
收藏

51CTO技术栈公众号