混编C和C++
Table of Contents
C++代码中嵌入C语言,是可能的;C语言中嵌入C++也是可能的。这里将介绍如何将它们混编,如何解决混编时出现的问题。
有些编译器不支持混编,但clang和gcc都是支持的,这里使用的就是这两种编译器。
1 声明链接规范
混编C和C++是C++提供的功能,所以只能在C++代码中想办法。C++编译器允许在声明中带 extern "C" ,表示按照C的方式链接,这样声明的代码可以与C交互。 extern "language" 是可以嵌套的:
extern "C" { void a(); // C linkage extern "C++" { void b(); // C++ linkage extern "C" void c(); // C linkage } extern "C++" void d(); // C++ linkage }
2 C++中访问C
C++中引用C必须在C的声明中带 extern "C" 。最简单的引用C语言代码的方式,是在C++ 原代码文件中,在包含头文件的地方使用 extern "C" :
// 通过将头文件包含包括在 extern "C" 中的方式, // 将C代码的声明全部指定为 extern "C" extern "C" { #include "header_of_c.h" } // ... // 之后便可以正常使用 C 函数 std::cout << c_code_get_string() << std::endl;
如果你希望你的C代码可以被C或C++引用,可以在C头文件中将所有声明放在 extern "C" 大括号里,但C编译器不认识这个语法,需要在C编译器使用头文件的时候,就排除掉 extern "C" ,C++编译器使用头文件的时候,就包含 extern "C" 。所有的C++编译器都预定义了宏 __cplusplus ,所以在C的头文件可以是如下结构:
// 这是几乎每个头文件都需要的,保证头文件只被包含一次 #ifndef xxx_HEADER_GUARD_H_ #define xxx_HEADER_GUARD_H_ // 如果使用C++编译器,则将头文件中所有声明包含在 extern "C" 中 #ifdef __cplusplus extern "C" { #endif // __cplusplus // ... body of header #ifdef __cplusplus } // closing brace for extern "C" #endif // __cplusplus #endif // xxx_HEADER_GUARD_H_
有时候,你可能希望使用C++的方式使用一个C的数据结构,这就需要使用派生。声明一个 C++的类,以C的数据结构为父类,再定义成员函数,在成员函数中直接调用C的操作函数即可。
// 在 C 代码 buf.h 中 struct buf { char *data; size_t len; }; int buf_print(struct buf* buf); int buf_compare(struct buf* a, struct buf* b); // ... // 在C++代码中 extern "C" { #include "buf.h" } class BufInCPP : public buf { BufInCPP(): data(0), len(0) {} int print() { return buf_print(this); } int compare() { return buf_compare(this); } };
这样,就将一个C的数据结构转化为C++的数据结构。这不是必须的,但这能让C++代码拥有更统一的接口。需要注意的是,在析构结构时,要认真考虑是否需要让C代码释放内存(free)。
3 在C中访问C++
用 extern "C" 声明的函数可以被C访问。例如,有一个C++函数:
int print(int i, double d) { std::cout << "i=" << i << ", d=" << d; }
在头文件中,可以将其声明为 extern "C"
:
#ifdef __cplusplus extern "C" #endif int print(int i, double d);
这样,C语言代码只要包含这个头文件,就可以使用 print() 函数了。
C++是支持多态和模板的,同一个函数名可以输入不同的参数类型,但C不能那么做,需要区分函数名才能使用多态或者模板的C++函数。例如,在C++代码中,有一个模板函数f(),要让C语言能访问这个函数,需要重新写几个函数包含它,曲线救国:
template<typename T> T f(T v) {/* ... */} int f_int(int v) { return f(v); } double f_double(double v) { return f(v); }
在和C语言共享的头文件中,需要添加 extern "C" 声明:
#ifdef __cplusplus extern "C" { #endif int f_int(int v); double f_double(double v); #ifdef __cplusplus } #endif
4 C访问C++类
要访问C++类,最好的办法是将C++类包含在C的数据结构中,或者在提供C的接口时,使用 void* 来表示对象的类型。也有直接使用的办法,没有多少区别。
假设有一个C++类:
class A { public: int f(); // ... }; // 定义C语言接口函数 int A_f(A* a) { return a->f(); }
在C的头文件中,要这样声明:
#ifdef __cplusplus extern "C" { #endif struct A; int A_f(struct A); #ifdef __cplusplus } #endif
这样就可以通过调用 A_f() 来调用 A::f() 了。
5 函数指针
函数指针在混编C和C++时容易出错,C++有时候不知道函数指针参数到底是C还是C++。
typedef int (*fn)(int); extern "C" void caller(fn); extern "C" int my_fn(int); // ... caller(my_fn); // 编译错误
上面的例子中,fn 是指向C++函数的指针,my_fn 是 C 的函数,这是个类型错误。要编译通过,只需将相关函数都声明成C的链接方式:
extern "C" { typedef int (*fn)(int); void caller(fn); int my_fn(int); } caller(my_fn); // 编译成功
6 异常和long_jmp
最好的方法就是不要抛出异常,不要使用 long_jmp。如果必须要使用的话,异常不要超出 C++的范围,long_jmp不要跨越C++代码。
7 编译和链接
C语言代码使用 C 编译器编译,C++代码使用C++编译器编译。生成的 .o 或者 .a 文件正常使用C编译器链接即可。
CC -c a.c CXX -c b.cpp CC -o programname a.o b.o