34-如何在同一个程序中结合C++和C
第六章,杂项讨论
34-如何在同一个程序中结合C++和C
一、编译器兼容性
C++和C混合编程的首要前提是确保两个编译器产生兼容的目标文件(.obj、.lib、.dll等)。
所谓”兼容”,指的是编译器在”编译器相依的特性”上保持一致,例如:
int 和 double 的字节大小
参数压栈机制
内存对齐方式
调用约定(calling convention)
只有在这个基础上,才能讨论结合使用C++和C模块的问题。这个问题在语言标准化中被忽略了,所以唯一的办法是两个编译器的生产商承诺它们之间兼容
Name Mangling(名称重整)
Name Mangling是C++用于支持函数重载的机制。编译器会给程序的每个函数换一个独一无二的名字,由函数名和参数组合生成新的名称。由于C不支持函数重载,因此C不需要Name Mangling。
问题场景: 如果C++要调用C函数库的某个函数,比如 drawLine(int x1, int y1, int x2, int y2) ,C++编译器可能将其变换为 drawLine_int_int 之类的名字。但C函数库中的函数仍然叫 drawLine ,导致链接器找不到符号,链接失败。
解决方案:使用 extern “C”
// 对单个函数禁止Name Mangling |
extern “C” 只对接下来的一个函数有效。如果要同时为多个函数使用,可以使用大括号:
extern "C" { |
跨语言头文件设计:
有时代码既可能被C++使用,也可能被C使用。可以利用C++预定义的 __cplusplus 宏,使头文件可以”通用于数种语言”
|
注意: 如果要结合使用C、汇编、Fortran等语言的函数库,也需要抑制Name Mangling而使用 extern “C” 。并没有 extern “Fortran” 这样的用法,因为 extern “C” 并不是说函数用C写成,而是指明要抑制Name Mangling
三、静态初始化(Static Initialization)
程序的入口实际上并不是 main ,而是编译器提供的特殊函数(如 mainCRTStartup 等)。
在 main 执行前,需要进行静态初始化:全局对象(包括static类对象、全局对象、namespace作用域内的对象以及文件作用域内的对象)的构造函数通常在 main 被执行前就被调用。同样,这些对象的析构函数在 main 结束后被调用 [^4^]。
关键问题: 由于C++支持动态初始化(如全局变量 int b = a; ),而C仅支持静态初始化,如果 main 函数是用C写的,C++静态对象可能无法被正确初始化和析构。
解决方案:用C++写 main()
// C程序库的函数,用于完成主函数功能 |
只要程序的任意部分是C++写的,就应该用C++写 main() 函数。这能确保静态对象的构造和析构被正确处理
四、动态内存分配
通行规则很简单:C++部分使用 new 和 delete ,C部分使用 malloc (或其变种)和 free 。
C++的 new 和 delete 在分配和释放内存的同时,还会自动调用对象的构造函数和析构函数。而C的 malloc 和 free 只涉及内存的分配和释放,不会调用任何构造函数或析构函数。此外, new 和 delete 可以被重载,而 malloc 和 free 则不能。
绝对不能混用这两种内存管理机制。如果用 free 去释放 new 分配的内存,或者用 delete 去释放 malloc 分配的内存,都会导致未定义行为。只要坚持 new 分配的内存使用 delete 释放, malloc 分配的内存使用 free 释放,就不会有问题。
五、数据结构的兼容性
C和C++对于 struct 和内置类型变量是兼容的。因为C和C++对于 struct 的内存布局是相同的,假设在两类编译器下定义的同一结构将按同样的方式进行处理是安全的。
C++版本的结构体可以包含非虚成员函数,这不会影响与C的兼容性,因为非虚成员函数不会改变对象的内存布局,C虽然无法调用这些成员函数,但结构的内存布局保持不变。然而,一旦结构体中加入了虚函数,就会引入虚函数表指针(vptr)和虚函数表(vtbl),这会改变内存布局,导致与C不兼容。同样,继承(特别是虚基类)也会改变内存结构,使得C++和C之间的数据结构传递出现问题。
因此,在C++和C之间传递数据结构是安全的,前提是结构的定义式在C++和C中都可以编译。C++版本可以包含非虚成员函数,但其他改变如虚函数、继承等将产生影响,应尽量避免。
六、总结
如果想在同一程序中混合C++与C编程,记住以下指导原则:
- 确保C++和C编译器产生兼容的obj文件。
- 将在两种语言下都使用的函数声明为 extern “C” 。
- 只要可能,用C++写 main() 。
- 总用 delete 释放 new 分配的内存;总用 free 释放 malloc 分配的内存。
- 将在两种语言间传递的东西限制在用C编译的数据结构的范围内;这些结构的C++版本可以包含非虚成员函数。
