如何混合使用C和C++ (转)

 如何混合使用C和C++(上)

 

[32] How to mix C and C++ 如何混合使用CC++
(Part of
C++ FAQ Lite, Copyright © 1991-2006, Marshall Cline, [email protected])
Translator: Qiu Longbin <robin.qiu(at)yeah.net>
 
FAQs in section [32]:
·       [32.1] 混合使用CC++代码时我需要知道什么?
·       [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] 混合使用CC++代码时我需要知道什么?
下面是是一些要点(尽管一些编译器厂商可能并不需要所有这些;请检查你的编译器厂商的文档):
·       你必须使用你的 C++ 编译器编译你的 main() (例如,静态初始化)
·       你应该用 C++ 编译器接管链接过程(例如,这样可以获得它的特别的库)
·       你的 C C++ 编译器可能需要来自于相同的厂商,并且是兼容的版本(这样,它们才具备相同的调用约定)
另外,你应该需要继续阅读本文剩下的部分,找出如何才能在 C++ 中调用 C 函数 和/或 如何在 C 中调用 C++ 函数。
BTW ?也有另一个途径来处理这一整个事情,即:用一个 C++ 编译器来了编译你所有的代码(甚至是你的 C 风格代码)。那样,相当程度上就排除了混合 C C++ 的需要,加上它将使得你得在你的 C 风格代码中更细心(并且更可能 希望如此! 发现一些 bugs )。下面( down-side )你需要以某种方式升级你的 C 风格代码,这基本上是由于 C++编译器比C编译器更仔细/更挑剔的原因关键是清除你的C风格代码所需的努力可能少于混合CC++,并且额外的好处是你清除了你的C风格代码。显然,如果你不能改变你的C风格代码,你无需多种选择。(比如,C风格代码是来自第三方库)。
 
[32.2] 如何在C++代码里include一个标准C头文件?
#include 一个标准头文件(比如 <cstdio> , 你通常不必作任何事。比如 :
 //  这是C++代码
 
 #include <cstdio>  // #inlcude行没有什么不寻常的

 
 int main()
 {
   std::printf("Hello world/n");  // 调用也没什么不寻常的

   ...

 }
如果你认为 std::printf() 调用中 std:: 部分不寻常,那么你最好 克服它( get over it 。这句话的意思是使用标准库中名字的标准方式,因此,你现在就应该习惯它。
然而,如果你正在使用 C++ 编译器编译 C 代码,你可能不想把所有的 printf() 的调用转换成 std::printf() 。幸运的是,这种情况下 C 代码可以使用旧风格的头文件 <stdio.h> 而不是新风格头文件 <cstdio> namespace 的怪诞。
 /* 这是C代码,这里用C++编译器编译 */
 
 #include <stdio.h>          /* #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头文件(如<cstdio> 你可以改变的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 )的开发者写出出众的软件。
 

 

 

 

 

如何混合使用C和C++(下)

 

Mixing C and C++ Code in the Same Program


By Stephen Clamage, Sun Microsystems, Sun ONE Studio Solaris Tools Development Engineering


Translator: Qiu Longbin <robin.qiu(at)yeah.net>


C++语言提供了一个混合代码的机制,使得代码可以在同一个程序中被兼容的CC++编译器编译。在你移植代码到不同的平台和编译器时,你会体验到不同的成功度。本文展示了当你混合使用C,C++时,如何解决出现的一般的问题。文中所有情况,展示了使用Sun CC++编译器时所要做的事情。(译注:GCCgccg++也是这一对组合。)


内容
 
使用兼容的编译器
在C++ 源代码中访问C 代码
在C 源代码中访问C++ 代码
混合IOstream 和C 标准I/O
使用指向函数的指针
使用C++ 异常
链接程序

使用兼容的编译器


混合代码的第一个要求就是你使用的C和C++编译器必须是兼容的。他们必须以同样的方式,(例如),定义了基本类型如int, float或指针。Solaris操作系统指定了C程序的应用程序的二进制接口(ABI),它包含关于基本类型和函数如何被调用的信息。任何Solaris Os上可用的编译器都必须遵循ABI。

Sun C和C++编译器遵循Solaris OS ABI并且是兼容的。第三方的Solaris OS C编译器也必须遵循ABI。任何与Solaris Os兼容的C编译器也同样与Sun C++编译器兼容。

被你的 C 编译器使用的 C 运行时库也必须同 C++ 编译器兼容。 C++ 包括了标准 C 运行时库作为其子集,只有稍许不同。如果 C++ 编译器提供了自己版本的 C 头文件,那么这些头文件的版本被 C 使用时也必须是兼容的。

Sun C和C++编译器使用兼容的头文件,并且使用同样的C运行时库。他们是完全兼容的。


C++ 源代码中访问 C 代码

C++语言提供了一个“链接规范(linkage specification)”,用它你可以声明函数或对象遵循特定语言的程序链接约定。对象和函数的默认链接是C++的。所有C++编译器也为兼容的C编译器提供了C链接。

当你需要访问一个用C链接编译的函数(例如,某个函数被C编译器编译),就要声明那个函数具备C链接(译注:在C++代码中)。即使大多数C++编译器对C和C++数据对象的链接没有什么不同,你也需要在你的C++代码中声明C数据对象(data objects)具有C链接。类型(types)没有C或C++链接,除了指向函数的指针(pointer-to-function)类型。


声明链接规范

使用下述标记之一来声明一个对象或函数具备某种语言 language_name 的链接。
       extern "language_name" declaration ; 
extern "language_name" { declaration ; declaration ; ... }
第一个标记指定了紧随其后的声明(或定义)具有语言 language_name 的链接约定。第二个标记指定花括号内的所有都具有 language_name 的链接。注意,第二个标记的最后花括号后面不要有分号。
你可以嵌套链接规范,他们没有创建一个范围域( scope )。考虑下面的例子:
       extern "C" {    
     void f(); // C linkage     
     extern "C++" {        
           void g(); // C++ linkage
          extern "C" void h(); // C linkage
          void g2(); // C++ linkage     
     } 
     extern "C++" void k();// C++ linkage     
     void m(); // C linkage 
}


所有上面的函数都在相同的全局域,尽管是嵌套了链接规范。

 

C++代码中包含C头文件

如果你想使用一个C库,它定义的头文件意欲为C编译器所准备,你可以在extern “C”花括号中包含这个头文件:

       extern "C" {
     #include "header.h"
 }


Warning-警告-

不用为Solaris OS上的系统头文件使用该技术。Solaris头文件,并且所有赖于Sun C和C++编译器的头文件都已经为C和C++编译器做好了准备。如果你指定了一个链接,你可能使得声明于Solaris头文件中的声明失效。



创建混合语言(Mixed-Languge)的头文件

如果你想使得头文件同时适合于C和C++编译器,你可能把所有声明都放置在了extern “C”花括号中,但是C编译器并不认识这些语法。每个C++编译器都预定义了宏__cplusplus,这样你就可以使用这个宏来防卫C++语法扩展:

       #ifdef __cplusplus 
extern "C" { 
#endif  ... 
/* body of header */  
 
#ifdef __cplusplus
 } /* closing brace for extern "C" */ 
#endif


C structs增加C++特征

假定你想在你的C++代码中更容易地使用C库。并且假定你不使用C风格的访问方式,你可能想增加成员函数,或许虚函数,也可能从class派生等等。你如何完成这个变换并确保C库函数仍然能识别你的struct?考虑下面这个例子中C的struct buf的使用:

/* buf.h */ 
       struct buf {     
       char* data; 
       unsigned count; 
}; 
void buf_clear(struct buf*); 
int buf_print(struct buf*); /* return status, 0 means fail */ 
int buf_append(struct buf*, const char*, unsigned count); /* same return */

你想把这个struct转变进C++ class,并做下述改变,使它更容易使用:

       extern "C" {   
   #include "buf.h" 
} 
class mybuf { // first attempt -- will it work? 
public:    
      mybuf() : data(0), count(0) { }     
      void clear() { buf_clear((buf*)this); }  
      bool print() { return buf_print((buf*)this); } 
      bool append(const char* p, unsigned c) { return buf_append((buf*)this, p, c); }
private:   
      char* data;    
      unsigned count; 
};

class mybuf的接口看来更象C++代码,并且更容易整合进面向对象风格的编程 ─ 如果它可行的话。

当成员函数传递 this 指针给 buf 函数时发生了什么? C++ 类布局( layout )匹配 C 布局吗? this 指针指向数据成员,还是指向 buf ?如果增加虚函数到 mybuf 又会如何呢?

C++标准对buf和class mybuf的兼容性没有任何保证。这里的代码,没有虚函数,可以工作,但你不能指望这个。如果你增加了虚函数,这个代码会失败,因为编译器增加了额外的数据(比如指向虚表的指针)放在class的开始处。

可移植的方案是把struct buf单独放着不动它,尽管你想保护数据成员并仅仅通过成员函数提供访问。仅当你不改变声明的情况下,你才能保证C和C++的兼容性。

你可以从C struct buf派生出一个C++ class mybuf,并且传递指向基类buf的指针给mybuf的成员函数。当转换mybuf* 到 buf*时,如果指向mybuf的指针没有指向buf数据的起始处,C++编译器会自动调整它。mybuf的布局在C++编译时可能发生改变,但是操纵mybuf和buf对象的C++源代码将到处都可以工作。下面的例子展示了一个可移植方法给C struct增加C++和面向对象特征:

       extern "C" {   
    #include "buf.h" 
} 
class mybuf : public buf { // a portable solution 
public:
      mybuf() : data(0), count(0) { }
      void clear() { buf_clear(this); }
      bool print() { return buf_print(this); }
     bool append(const char* p, unsigned c) { return buf_append(this, p, c); } 
};


C++ 代码可以自由的创建和使用 mybuf 对象,传递它们到那些期望 buf 对象的 C 代码,并且可以结合地很好。当然如果你为 mybuf 增加了数据成员, C 代码就不知道它们。那就是一般类设计的考虑。你要当心要一致性地创建和删除( delete ) buf 和 mybuf 对象 . 让 C 代码删除 (free) 一个有 C 代码创建的对象是最安全的,并且不允许 C 代码删除一个 mybuf 对象。


C 源代码中访问 C++ 代码:

如果你声明一个 C++ 函数具有 C 链接,它就可以在由 C 编译器编译的函数中被调用。一个声明具有 C 链接的函数可以使用所有 C++ 的特征,如果你想在 C 代码中访问它,它的参数和返回值必须是在 C 中可访问的。例如,如果一个函数声明有一个 IOstream 类的引用作为参数,就没有(可移植的)方法来解析这个参数类型给 C 编译器。 C 语言没有引用,模板,或具备 C++ 特征的 class.
这里是一个具备 C 链接的 C++ 函数的例子:
       #include <iostream> 
extern "C" 
int print(int i, double d) 
{     
    std::cout << "i = " << i << ", d = " << d; 
}



你可以在头文件中声明一个函数 print ,被 C 和 C++ 代码共用:
       #ifdef __cplusplus
 extern "C" 
#endif  
int print(int i, double d);


你可以至多声明重载集中的一个函数作为extern “C”,因为一个C函数仅仅可以有一个给定的名字。如果你要在C中访问重载函数,你可以以不同的名字写出C++ wrapper函数,见下面的例子:

       int g(int); 
double g(double); 
extern "C" int g_int(int i){ return g(i); }
 extern "C" double g_double(double d) { return g(d); }
这里是 C 头文件 wrapper 函数的例子:
       int g_int(int); 
double g_double(double);


你也需要包裹(wrapper)函数来调用template functions,因为template functions不能声明为extern “C”:

       template<class T> 
T foo(T t) { ... } 
 
extern "C" int foo_of_int(int t) { return foo(t); } 
extern "C" char* foo_of_charp(char* p) { return foo(p); }

C++代码仍然可以访问重载函数和template functions。C代码必须使用wrapper functions。


C中访问C++ class


能够从C代码中访问C++ class吗?你可以声明一个C struct,看上去象一个C++ class并能以某种方式调用成员函数吗?答案是肯定的,虽然你必须为维持可移植性增加一些复杂性。任何对C++ class的定义的修改都要求你重新审查你的C代码。

假定你有一个C++ class如下:

       class M { 
public:     
     virtual int foo(int);     // ... 
private:
     int i, j; 
};

你不能在C代码中声明class M。你能做的最好的事就是传递指向class M 对象的指针,这类似于在C标准I/O中传递FILE对象。你可以在C++中写extern “C”函数访问class M 对象并在C代码中调用这些函数。下面是一个C++函数,被设计来调用成员函数foo:

       extern "C" int call_M_foo(M* m, int i) { return m->foo(i); }
 

下面是C代码的一个例子,它使用了class M:

       struct M; /* you can supply only an incomplete declaration */ 
int call_M_foo(struct M*, int); /* declare the wrapper function */ 
int f(struct M* p, int j) /* now you can call M::foo */     
{ 
 return call_M_foo(p, j);
}


混合 IOstream C 标准 I/O

你可以在 C++ 程序中使用来自于标准 C 头文件 <stdio.h> 的 C 标准 I/O ,因为 C 标准 I/O 是 C++ 的一部分。
任何关于在同一个程序中混合 IOstream 和标准 I/O 的考虑都不依赖于程序是否以明确地包含 C 代码。这个问题对于纯粹的 C++ 程序使用标准 I/O 和 IOstream 是一样的。

Sun C和C++使用同样的C运行时库,这在关于兼容的编译器小节中注明过了。使用Sun编译器,你可以在同一个程序中自由地在C和C++代码中使用标准I/O。

C++ 标准说,你可以在同一个目标“流( stream )”上混合使用标准 I/O 函数和 IOstream 函数,比如标准输入和输出流。但是 C++ 实现在它们的遵从度上可能不同。一些系统要求你在做任何 I/O 之前先显式地调用 sync_with_stdio() 函数。在同一个流或者文件上混合 I/O 风格,实现品在 I/O 的效能方面也不同。最差情况下,你得到为每个字符的输入输出做一个系统调用的结果。如果程序有大量的 I/O ,性能可能是不可接受的。
最安全的途径是对任一给定的文件 / 标准流,坚持使用标准 I/O 或 IOstream 风格。在一个文件或流上使用标准 I/O ,在另一个不同的一个文件或流上使用 IOstream ,不会导致任何问题。


使用指向函数的指针

指向函数的指针必须指明是否指向一个 C 函数或 C++ 函数,因为 C 和 C++ 函数可能采用不同的调用约定。否则,编译器不知道究竟要产生哪种函数调用的代码。多数系统对 C 和 C++ 并没有不同的调用约定,但是 C++ 允许存在这种可能性。因此你必须在声明指向函数的指针时要小心,确保类型匹配。考虑下面的例子:
       typedef int (*pfun)(int); // line 1 
extern "C" void foo(pfun); // line 2 
extern "C" int g(int) // line 3 ... 
foo( g ); // Error! // line 5


Line 1声明了pfun指向一个C++函数,因为它缺少链接说明符。
Line 2声明foo为一个C函数,它具有一个指向C++函数的指针。
Line 5试图用指向g的指针调用foo,g是一个C函数,所以类型不匹配。

要确保指向函数的指针的链接规范与它将要指向的函数匹配。在下面这个正确的例子中,所有声明都包含在extern “C”花括号中,确保了类型匹配。

       extern "C" {     
    typedef int (*pfun)(int);
    void foo(pfun);    
    int g(int);
} 
foo( g ); // now OK
指向函数指针有另外一个微妙之处,它可能给程序员带来陷阱。链接规范应用于函数所有的参数类型和返回类型上。如果你将一个详细声明的指向函数的指针( pointer-to-function )用作函数参数,链接规范也同样地作用在了这个指向函数的指针上。如果你通过使用 typedef 声明了一个指向函数的指针,这个 typedef 类型在用于函数声明中时,链接规范不会受到影响。例如,考虑下面的代码:
       typedef int (*pfn)(int); 
extern "C" void foo(pfn p) { ... } // definition 
extern "C" void foo( int (*)(int) ); // declaration
前两行可以出现在一个程序文件中,第三行可以出现在一个头文件中,在此头文件中你不想暴露出内部使用的 typedef 的名字。尽管你想让 foo 的声明和定义相匹配,但它们不匹配。 foo 的定义接受一个指向 C++ 函数的指针,但是其声明接受一个指向 C 函数的指针。这段代码声明了一对重载函数。(译注:在此是参数类型的链接规范不同。)
为了避免这个问题,得在声明中一致地使用 typedefs ,或者以某个适当的链接规范包围 typedefs 。例如,假定你想让 foo 接受一个指向 C 函数的指针,你可以以下面的方式写出 foo 的定义:
       extern "C" {    
    typedef int (*pfn)(int);  
    void foo(pfn p) { ... }
 }


使用 C++ 异常



传播(
Propagating )异常
从 C 函数中调用 C++ 函数,并且 C++ 函数抛出了一个异常,将会发生什么?在是否会使得该异常有适当的行为这个问题上 C++ 标准有些含糊,并且在一些系统上你不得不采取特别的预防措施。一般而言,你必须得求诸用户手册来确定代码是否以适当的方式工作。

Sun C++中不需要预防措施。Sun C++中的异常机制不影响函数调用的方式。当C++异常被抛出时,如果一个C函数正处于活动状态,C函数将转交给异常处理过程。

混合异常和set_jmplong_jmp
最好的建议是在包含C++代码的程序中不要使用long_jmp。C++异常机制和C++关于销毁超出作用域对象的规则可能被long_jmp违反,从而得到不确定的结果。一些编译器整合了异常和long_jmp,允许它们协同工作,但你不能依赖这样的行为。Sun C++使用与C编译器相同的set_jmp和long_jmp。

许多 C++ 专家相信 long_jmp 不应该与异常整合,因为很困难准确地指定它该如何作为。

如果你在混合有C++的C代码中使用long_jmp,要确保long_jmp不要跨越(cross over)活动的C++函数。如果你不能确保这点,查看一下是否你可以通过禁用异常来编译那个C++代码。如果对象的析构器被绕过了,你仍旧可能有问题。


链接程序


某时,多数C++编译器要求main函数要被C++编译。这个要求今天来说并不常见,Sun C++就不要求这点。如果你的C++编译器需要编译main函数,但你由于某种原因不能这么做,你可以改变C main函数的名字并从一个C++ main的包裹函数中调用它。例如,改变C main函数的名字为C_main,并写如下C++代码:

       extern "C" int C_main(int, char**); // not needed for Sun C++ 
int main(int argc, char** argv) { return C_main(argc, argv); }

当然,C_main必须是被声明在C代码中,并返回一个int。如上注解,使用Sun C++是不会有这个麻烦。

即使你的程序主要由 C 代码构成,但使用了 C++ 库,你需要链接 C++ 运行时以支持与 C++ 编译器提供的库一起编译进程序。做这件事的最简单和最好的方式是使用 C++ 编译器驱动链接过程。 C++ 编译器的驱动器知道要链接什么库,次序如何。特定的库可以依赖编译 C++ 代码时使用的选项。

假定你有C程序文件main.o, f1.o和f2.o,你可以使用C++程序库helper.a。用Sun C++,你要如下引发命令行:

       CC -o myprog main.o f1.o f2.o helper.a
必要的 C++ 运行时库,如 libCrun 和 libCstd 被自动地链接进去。 helper.a 可能要求使用额外的链接选项。如果你由于某种原因不能使用 C++ 编译器,你可以使用 CC 命令的 dryrun 选项来获得编译器引发出的命令列表,并把它们捕获进一个 shell 脚本。因为确切的命令 ( 复数 ) 依赖于命令行选项,你应该复查— dryrun 选项输出和命令行的任何一个变动。


更多信息
 

Sun ONE Studio C/C++ Documentation
 Sun ONE C和C++编译器的最新信息,包括man手册页和readme文件。



关于作者

Steve Clamage从1994年在Sun至今?。它当前是C++编译器和Sun ONE Studio编译器套件的技术领导。它从1995年开始是ANSI C++委员会的主席。

你可能感兴趣的:(C++,c,struct,Solaris,iostream,编译器)