C++中extern "C"的作用是什么?

C++中extern “C”的作用是什么?

技术背景

C++是在C语言的基础上发展起来的面向对象编程语言,它支持函数重载等特性。为了实现函数重载,C++编译器会对函数名进行名称修饰(Name Mangling),即在编译过程中把函数名和其参数类型等信息组合成一个新的字符串作为符号名,以便在链接时能区分不同的重载函数。而C语言不支持函数重载,函数名就是其在链接时的符号名。当需要在C++代码中调用C语言编写的函数,或者让C语言代码调用C++函数时,由于名称修饰的差异,会导致链接错误。extern "C"就是为了解决这个问题而引入的。

实现步骤

1. 在C++代码中调用C函数

  • 编写C头文件(c.h)
1
2
3
4
5
6
7
8
9
10
11
12
#ifndef C_H
#define C_H

#ifdef __cplusplus
extern "C" {
#endif
int f();
#ifdef __cplusplus
}
#endif

#endif

在这个头文件中,使用#ifdef __cplusplus来判断是否是C++编译器。如果是,则将函数声明放在extern "C"块中,以告诉C++编译器这些函数采用C语言的链接方式,不进行名称修饰。

  • 编写C源文件(c.c)
1
2
3
#include "c.h"

int f(void) { return 1; }

这里实现了头文件中声明的函数f

  • 编写C++源文件(main.cpp)
1
2
3
4
5
6
#include <cassert>
#include "c.h"

int main() {
assert(f() == 1);
}

在C++代码中包含C头文件,并调用C函数f

  • 编译和链接
1
2
3
4
g++ -c -o main.o -std=c++98 main.cpp
gcc -c -o c.o -std=c89 c.c
g++ -o main.out main.o c.o
./main.out

这样就可以正确编译、链接并运行程序。

2. 在C代码中调用C++函数

  • 编写C++头文件(cpp.h)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#ifndef CPP_H
#define CPP_H

#ifdef __cplusplus
// C cannot see these overloaded prototypes, or else it would get confused.
int f(int i);
int f(float i);
extern "C" {
#endif
int f_int(int i);
int f_float(float i);
#ifdef __cplusplus
}
#endif

#endif

在头文件中,将C++的重载函数声明放在extern "C"块外,而将为C代码提供的非重载函数声明放在extern "C"块内。

  • 编写C++源文件(cpp.cpp)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include "cpp.h"

int f(int i) {
return i + 1;
}

int f(float i) {
return i + 2;
}

int f_int(int i) {
return f(i);
}

int f_float(float i) {
return f(i);
}

实现C++的重载函数和为C代码提供的非重载函数。

  • 编写C源文件(main.c)
1
2
3
4
5
6
7
8
#include <assert.h>
#include "cpp.h"

int main(void) {
assert(f_int(1) == 2);
assert(f_float(1.0) == 3);
return 0;
}

在C代码中调用为C代码提供的非重载函数。

  • 编译和链接
1
2
3
4
gcc -c -o main.o -std=c89 -Wextra main.c
g++ -c -o cpp.o -std=c++98 cpp.cpp
g++ -o main.out main.o cpp.o
./main.out

同样可以正确编译、链接并运行程序。

核心代码

extern "C"的两种语法形式

1
2
3
4
5
6
7
8
9
// 形式1
extern "C" void foo(int);

// 形式2
extern "C"
{
void g(char);
int i;
}

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
using namespace std;

extern "C"
{
#include <stdio.h> // Include C Header
int n; // Declare a Variable
void func(int,int); // Declare a function (function prototype)
}

int main()
{
func(1, 2); // Calling function . . .
return 0;
}

// Function definition . . .
void func(int m, int n)
{
// 函数实现
}

最佳实践

  • 使用条件编译:在C头文件中使用#ifdef __cplusplusextern "C"结合,使头文件既可以被C代码包含,也可以被C++代码包含。
  • 避免在extern "C"块中使用需要名称修饰的C++特性:如函数重载、模板等,否则会导致编译错误。
  • 注意变量和函数的声明顺序:如果变量或函数在之前已经以非extern "C"的方式声明,后续再以extern "C"声明可能会出现问题。

常见问题

1. 为什么在extern "C"块中不能使用函数重载和模板?

因为extern "C"的目的是让函数采用C语言的链接方式,不进行名称修饰,而函数重载和模板需要名称修饰来区分不同的函数,所以在extern "C"块中使用这些特性会导致编译错误。

2. 如果忘记使用extern "C"会怎样?

在C++代码中调用C函数时,如果忘记使用extern "C",C++编译器会对C函数名进行名称修饰,而C编译器生成的函数符号名没有经过修饰,链接时会找不到对应的函数,导致链接错误。在C代码中调用C++函数时也会出现类似问题。

3. extern "C"对类型定义有影响吗?

extern "C"不影响类型定义,它主要影响函数和变量的链接方式。

4. 不同编译器对extern "C"的处理有差异吗?

不同编译器对extern "C"的基本处理是一致的,但可能存在一些细微差异。例如,使用Visual Studio编译器时,extern "C"符号会在前面加一个下划线。在使用时需要注意这些差异。


C++中extern "C"的作用是什么?
https://119291.xyz/posts/what-is-the-effect-of-extern-c-in-cpp/
作者
ww
发布于
2025年5月20日
许可协议