[32] How to mix C and C++ 如何混合使用C和C++
(Part of C++ FAQ Lite, Copyright © 1991-2006, Marshall Cline, [email protected])
Translator: Qiu Longbin
FAQs in section [32]:
·
[32.1] 混合使用C和C++代码时我需要知道什么?
·
[32.2] 如何在C++代码里include一个标准C头文件?
·
[32.3] 在我的C++代码中如何include一个非系统的C头文件?
·
[32.4] 我要如何修改我自己的C头文件,以使得在C++代码中更容易地#inlcude它们?
·
[32.5] 我如何才能在我的C++代码中调用一个非系统的C函数f(int, char, float)?
·
[32.6] 如何创建一个C++函数f(int, char, float),它可以被我的C代码调用?
·
[32.7] C/C++函数被C++/C函数调用时,为何链接器给出错误信息
·
[32.8] 如何能够传递一个C++类对象到/从一个C函数?
·
[32.9] 我的C函数可以直接访问C++类对象中的数据吗?
·
[32.10] 为什么我感觉到相对于C,在C++中我“更远离机器层面”?
[32.1] 混合使用C和C++代码时我需要知道什么?
下面是是一些要点(尽管一些编译器厂商可能并不需要所有这些;请检查你的编译器厂商的文档):
·
你必须使用你的
C++
编译器编译你的
main()
(例如,静态初始化)
·
你应该用
C++
编译器接管链接过程(例如,这样可以获得它的特别的库)
·
你的
C
和
C++
编译器可能需要来自于相同的厂商,并且是兼容的版本(这样,它们才具备相同的调用约定)
另外,你应该需要继续阅读本文剩下的部分,找出如何才能在
C++
中调用
C
函数
和/或
如何在
C
中调用
C++
函数。
BTW
?也有另一个途径来处理这一整个事情,即:用一个
C++
编译器来了编译你所有的代码(甚至是你的
C
风格代码)。那样,相当程度上就排除了混合
C
和
C++
的需要,加上它将使得你得在你的
C
风格代码中更细心(并且更可能
—
希望如此!
—
发现一些
bugs
)。下面(
down-side
)你需要以某种方式升级你的
C
风格代码,这基本上是由于
C++编译器比C编译器更仔细/更挑剔的原因。关键是清除你的C风格代码所需的努力可能少于混合C和C++,并且额外的好处是你清除了你的C风格代码。显然,如果你不能改变你的C风格代码,你无需多种选择。(比如,C风格代码是来自第三方库)。
[32.2] 如何在C++代码里include一个标准C头文件?
#include
一个标准头文件(比如
)
,
你通常不必作任何事。比如
:
// 这是C++代码
#include // #inlcude行没有什么不寻常的
int main()
{
std::printf("Hello world/n"); // 调用也没什么不寻常的
...
}
如果你认为
std::printf()
调用中
std::
部分不寻常,那么你最好
“
克服它(
get over it
)
”
。这句话的意思是使用标准库中名字的标准方式,因此,你现在就应该习惯它。
然而,如果你正在使用
C++
编译器编译
C
代码,你可能不想把所有的
printf()
的调用转换成
std::printf()
。幸运的是,这种情况下
C
代码可以使用旧风格的头文件
而不是新风格头文件
和
namespace
的怪诞。
/* 这是C代码,这里用C++编译器编译 */
#include /* #inlcude行没有什么不寻常的 */
int main()
{
printf("Hello world/n"); /* 调用也没什么不寻常的 */
...
}
最后评论:如果你的
C
头文件
不是
标准库的一部分,我们为你准备有稍微不同的方针。存在两种情况
你不能改变头文件
,或者
你可以改变头文件
。
[32.3] 在我的C++代码中如何include一个非系统的C头文件?
如果你要包含的
C
头文件不是由系统提供的,你可以把
#include
包裹在
extern “C” { /* here */ }
结构里。这就告诉
C++
编译器在头文件中声明的函数是
C
函数。
// 这是C++代码
extern "C" {
// 获得声明
f(int i, char c, float x)
#include "my-C-code.h"
}
int main()
{
f(7, 'x', 3.14); // 注意:调用没什么特别的
...
}
注意:稍微不同的方针应用:
由系统提供的C头文件(如)和
你可以改变的C头文件。
[32.4] 我要如何修改我自己的C头文件,以使得在C++代码中更容易地#inlcude它们?
如果你要
include
一个不是有系统提供的
C
头文件,并且你可以修改这个
C
头文件,你应该着重考虑在此头文件中增加
extern “C” {}
逻辑,这就能使得对于
C++
用户更容易地把它们包含进他们的
C++
代码中去。因为
C
编译器不理解
extern “C”
结构,你必须在
#ifdef
里面包裹
extern “C” {
和
}
行,让它们被
C
编译器忽略。
步骤
#1
:把下面三行放置在你的
C
头文件的最顶处(注意:符号
__cplusplus
只在编译器为
C++
编译器时才被定义):
#ifdef __cplusplus
extern "C" {
#endif
步骤
#2
:把如下三行放置在你的
C
头文件的最底部:
#ifdef __cplusplus
}
#endif
现在你可以在你的
C++
代码里
#includeC
头文件里而无需多余的
extern “C”
:
// 这是C++代码
// 获得声明
f(int i, char c, float x)
#include "my-C-code.h" // 注意:#include 行没什么特别的
int main()
{
f(7, 'x', 3.14); // 注意:调用没什么特别的
...
}
注意:
#include
宏
4
中不同方式下是有缺陷(
evil)
的:
evil#1[1] , evil#2[2] , evil#3[3] , and
evil#4。但是,它们常常仍很有用。
[32.5] 我如何才能在我的C++代码中调用一个非系统的C函数f(int, char, float)?
如果你有一个独立的
C
函数想调用,并且,由于一些原因,你不能或不想
#include
那个声明有此函数的头文件,你可以在你的
C++
代码里使用
extern “C”
语法声明这个独立的
C
函数。很自然,你需要使用函数的完全形式的原型:
extern "C" void f(int i, char c, float x);
多个
C
函数可以通过大括号被成组放在一个块中。
extern "C" {
void f(int i, char c, float x);
int g(char* s, const char* s2);
double sqrtOfSumOfSquares(double a, double b);
}
这之后,你只需把它们当作
C++
函数简单地调用他们就行了:
int main()
{
f(7, 'x', 3.14); // 注意:调用没什么特别的
...
}
[32.6] 如何创建一个C++函数f(int, char, float),它可以被我的C代码调用?
C++
编译器必须使用
extern "C" 结构来知道f(int, char, float)将被C编译器调用。
// 这是C++代码
// 使用
extern "C"声明
f(int,char,float) :
extern "C" void f(int i, char c, float x);
...
// 在某个C++模块中定义
f(int,char,float):
void f(int i, char c, float x)
{
...
}
extern “C”
行告知编译器传送给链接器的外部信息(
external information
)使用
C
调用约定和名字重整(
name mangling
)规则(比如,加一个前缀下划线)。因为名字重载(
name overloading
)不被
C
支持,所以你不能同时写几个被
C
程序调用的重载函数。
[32.7] C/C++函数被C++/C函数调用时,为何链接器给出错误信息?
如果你未能正确处理
extern “C”
,你将得到链接错误而不是编译错误。这归因于这样一个事实:
C++
编译器在函数名
“
重整(
mangle
)方面与
C
编译器常常不同。
”
请查看前面两个关于如何使用
extern “C”
的
FAQ.
[32.8] 如何能够传递一个C++类对象到/从一个C函数?
这里是一个例子(
extern “C”
的信息,参见前面两个
FAQs
)。
Fred.h
:
/* 这个头文件可以被C和C++编译器读取 */
#ifndef FRED_H
#define FRED_H
#ifdef __cplusplus
class Fred {
public:
Fred();
void wilma(int);
private:
int a_;
};
#else
typedef
struct Fred
Fred;
#endif
#ifdef __cplusplus
extern "C" {
#endif
#if defined(__STDC__) || defined(__cplusplus)
extern void c_function(Fred*); /* ANSI C prototypes */
extern Fred* cplusplus_callback_function(Fred*);
#else
extern void c_function(); /* K&R style */
extern Fred* cplusplus_callback_function();
#endif
#ifdef __cplusplus
}
#endif
#endif /*FRED_H*/
Fred.cpp
:
// 这是C++代码
#include "Fred.h"
Fred::Fred() : a_(0) { }
void Fred::wilma(int a) { }
Fred* cplusplus_callback_function(Fred* fred)
{
fred->wilma(123);
return fred;
}
main.cpp
:
// 这是C++代码
#include "Fred.h"
int main()
{
Fred fred;
c_function(&fred);
...
}
c-function.c
:
/* 这是C代码 */
#include "Fred.h"
void c_function(Fred* fred)
{
cplusplus_callback_function(fred);
}
不同于你的
C++
代码,除非指针是
完全一致
的类型,否则你的
C
代码不能判断出两个指针指向同一个对象。比如,在
C++
中,很容易检查一个
Derived*
的指针
dp
同
Base*
的指针
bp
一样指向了相同的对象:只要说
if (dp == bp ) ...C++
编译器自动把两个指针转换到相同的类型,在这个例子中,转换到
Base*
,然后再比较它们。依赖于
C++
编译器的实现细节,这个转换时常改变了指针值的位
(bits)
信息。然而,你的
C
编译器将不知道如何做那样的转换,因此,比如从
Derived*
到
Base*
的转换必须发生在用
C++
编译器编译的代码中,而不应该在用
C
编译的代码中。
注意:
当把两者转换成
void*
时你必须特别小心,因为这种转换将不允许
C
/
C ++
编译器做适当的指针调整!例如(接着前面的章节),你把
dp
和
bp
都赋值给
void*
指针
dpv
和
bpv
,你可能获得
dpv != bpv
的情况,即使
dp == bp
。同时你会收到警告信息。
[32.9] 我的C函数可以直接访问C++类对象中的数据吗?(译者:符合“POD”类型,就可以了)
有时可以。
(传递一个
C++
类对象到/从一个
C
函数的基本信息,参见前面的
FAQ
)
你可以安全地从
C
函数中访问
C++
对象的数据,如果
C++
类满足下面条件:
·
没有
virtual
函数(包括继承来的
virtual
函数)
·
所有数据都有处在同一个访问级段中(
private/protected/public
)
·
没有完全包含(
fully-contained
,译注:成员对象,即
“
包含
”
而非
“
聚合
”
)的带有
virtual
函数的子对象(
subobjects
)
如果
C++
类拥有很多基类(或者如果任一完全包含的子对象有多个基类),技术上而言访问其数据是不可移植的,因为继承之下的
class
布局(
layout
)上,语言并为强制。但是在实际上,所有
C++
编译器都几乎以同样的方式处理:基类对象第一个出现(多继承时按从左到右的次序),然后是成员对象。
更进一步,如果类(或者任意基类)含有一些
virtual
函数,几乎所有的
C++
编译器都放置一个
void*
在第一个
virtual
函数出现的位置或者在对象的最前面。还有就是,这些都不是语言要求的,但它是
“
每个编译器
”
都采取的方式。
如果类有
virtual
基类,这就更加复杂和移植性更差。通用的实现技术是让对象包含一个
virtual
基类对象
(V)
(无论
V
作为
virtual
基类出现在继承层次结构的什么地方)。对象的其余部分以通常的次序出现。每个派生的含有
V
作为
virtual
基类的对象,实际上拥有一个指针,指向最终对象的
V
部分。(译者:更多这方面的信息参见《
Inside C++ Object Model
》)
[32.10] 为什么我感觉到相对于C,在C++中我“更远离机器层面”?
因为你的确如此。
作为一个
OO
编成语言,
C++
允许你对问题域本身建模,这就允许你在问题域语言中编程而不是在解空间(
solution domain
)编程。
C
的一个强大之处在于它
“
没有隐藏什么机制(
no hidden mechanism
)
”
:你所见,即你所得。你可以读你的
C
程序并且
“
查看(
see
)
”
每个时钟周期。
C++
中就不是这么回事了;
一些老顽固的
C
程序员(比如我们中的一些曾)常常对这个特征很矛盾(你也可以说是
“
敌对
”
)。但是当他们完成了到
OO
思想的转变,他们就能时常体会到尽管
C++
对程序员隐藏了一些机制,它也提供了一个抽象层和表达上的经济(
economy of expression
)
(
译注:从成本上
)
,这降低了维护成本又不会降低运行时性能。
C++
并没有试图让差的程序员能够避免写出差的程序;它允许明理(
reasonable
)的开发者写出出众的软件。