extern "c"的用法:c与c++的互相调用
在我的印象里,extern "c"一直是c++调用c接口用的,用法是用exetrn "c"把相应写好的c接口包住。
即在 C++ 出现以前,很多代码都是 C 语言写的,而且很底层的库也是 C 语言写的,为了更好的支持原来的 C 代码和已经写好的 C 语言库,需要在 C++ 中尽可能的支持 C ,而 extern “C” 就是其中的一个策略。 extern “C” 主要作用就是为了能够正确实现 C++ 代码调用其他 C 语言代码。 extern “C” 会指示编译器这部分代码按 C 语言的进行编译,而不是 C++。
但是在最近的工作中同事提到c调用c++时同样用到extern "c"语法,惊讶之余把这块内容好好看了一下,有了很多收获:
1.到底什么是extern "c"
理解这个问题的关键在于函数重载: c++中支持函数重载,c语言不支持函数重载,这造成了c++和c语言的函数名解析不同。c语言函数名就是函数名,c++的函数名是函数名+参数组合起来的。
C
语言的函数名称解析仅仅基于函数名称;而C++
的函数名称解析基于函数名称和参数列表。
比如,函数void func(double a)
在C
和C++
中的编译阶段函数名称会被解析成什么呢?
在C
语言中,由于没有名称修饰,所以在编译时函数名称仍然是func
,不会因为参数类型或数量而改变。
在C++
中,由于名称修饰的存在,函数名称在编译阶段会被编译器转换成一个包含函数原型信息的唯一标识符。通常会涉及函数返回类型、参数类型以及参数数量。以GCC(GNU Compiler Collection)
为例,func(double a)
会被转换成_Z4funcd
,这里:
_Z
:是GCC
用来表示修饰名称的前缀4
:表示函数名称func
的的字符数d
:是double类型的编码
因此,用c++的方式去寻找c语言的符号是无法寻找到的。extern "C"
为何可以做到?
extern "C"
的作用就是修改了符号表的生成方式,将c++符号的生成方式换成了c的生成方式。
即
c库中生成的符号是c编译器的符号, 因此c语言可以直接链接。而c++程序需要使用extern "C"
让编译器使用c的符号命名方式去进行链接,这样才能找到对应的符号。
2.extern "c"到底是用来c++调用c,还是c
直接说答案:都可以。
3.c++ call c和 c call c++的实战
3.1Minimal runnable C from C++ example
main.cpp
#include <cassert> #include "c.h" int main() { assert(f() == 1); }
c.h
#ifndef C_H #define C_H /* This ifdef allows the header to be used from both C and C++ * because C does not know what this extern "C" thing is. */ #ifdef __cplusplus extern "C" { #endif int f(); #ifdef __cplusplus } #endif #endif
c.c
#include "c.h" int f(void) { return 1; }
run
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
Without extern "C"
the link fails with:
main.cpp:6: undefined reference to `f()'
3.2Minimal runnable C++ from C example
main.c
#include <assert.h> #include "cpp.h" int main(void) { assert(f_int(1) == 2); assert(f_float(1.0) == 3); return 0; }
cpp.h
#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
cpp.cpp
#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); }
run
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
Without extern "C"
it fails with:
main.c:6: undefined reference to `f_int' main.c:7: undefined reference to `f_float'
分析:着重看c调用c++,这里巧妙利用了#ifdef __cplusplus,使得当用cpp编译时,函数还是f。当没用c++编译时,函数就变成了f_int和f_float。这里的f_int和f_float就是调用了f。
这样就同时满足了c和c++调用该函数的需求,我即可以在c++中调用该接口,也可以在c语言中调用该接口。
注意:如果编译时直接用命令“g++ main.c cpp.cpp”则不会报错,即使没有用extern "C"。这是因为C++是兼容C的语法的,如果在编译的时候,c文件就是用g++编译的,则函数的命名规则会变得跟cpp文件中的函数一样,那么肯定不会报错了
4 进阶:C语言调用c++中的成员函数
在C程序中调用C++成员函数需要遵循几个步骤:
-
使用C++的
extern "C"
声明来导出函数,以便C可以链接到它。 -
确保C++对象实例的存在,并且在C中正确地通过函数指针调用成员函数
3. 可以单独搞出个wrapper.h放C++暴露出来的调用实例的成员函数的接口。因为这个接口不好放在cpp.h里,如果放在cpp.h里,则main.c需要#include "cpp.h",会因为cpp.h中有关于类的定义而报错(因为c语言中没有类的概念)
// main.c
#include "wrapper.h"
int main()
{
wrapper_hello();
}
// wrapper.h
#ifdef __cplusplus
#include <iostream>
#endif
#ifdef __cplusplus
extern "C"
{
#endif
void wrapper_hello();
#ifdef __cplusplus
}
#endif
// cpp.h
class A
{
public:
int i = 0;
void hello();
};
// cpp.cpp
#include "cpp.h" #include <cstdio> void A::hello() { printf("in Class A, hello!\n"); } A a; #ifdef __cplusplus extern "C" { #endif void wrapper_hello() { a.hello(); } #ifdef __cplusplus } #endif
run
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
Without extern "C"
it fails with:
$ g++ -o main.out main.o cpp.o /usr/bin/ld: main.o: in function `main': main.c:(.text+0xe): undefined reference to `wrapper_hello' collect2: error: ld returned 1 exit status
参考链接:
- What is the effect of extern "C" in C++? https://stackoverflow.com/questions/1041866/what-is-the-effect-of-extern-c-in-c
- extern "C"如何使用? https://zhuanlan.zhihu.com/p/709910309
- c++中的extern ”C“ https://www.zhihu.com/tardis/bd/art/634091433?source_id=1001
- c语言和c++的相互调用 https://blog.csdn.net/qq_29344757/article/details/73332501