C与C++混合编程问题总结

1.什么时候混合C和c++代码?

以下是一些要点:

  • 1.编译 main() 时必须使用 C++ 编译器(例如,用于静态初始化)
  • 2.您的 C++ 编译器应该指导链接过程(例如,以便它可以获取其特殊库)
  • 3.您的 C 和 C++ 编译器可能需要来自同一供应商并具有兼容的版本(例如,因此它们具有相同的调用约定)

此外,您需要阅读本节的其余部分,以了解如何使您的 C 函数可被 C++ 调用和/或您的 C++ 函数可被 C 调用。
顺便说一句,还有另一种方法可以处理这整个事情:使用 C++ 编译器编译所有代码(甚至是 C 风格的代码)。这几乎消除了混合C和c++的需要,而且它会使您更加小心(在你的c风格代码可能发现一些错误)。缺点是,您需要以某种方式更新C风格的代码,主要是因为c++编译器比C编译器更仔细/挑剔。重点是,清理C风格代码所需的工作可能比混合C和c++所需的工作要少,而且作为奖励,您可以清理C风格代码。显然,如果你不能改变你的c风格代码(例如,如果它来自第三方),你没有太多的选择。

2.如何从 C++ 调用 C 函数?

只需声明C函数extern “C”(在你的c++代码中)并调用它(从你的C或c++代码中)。例如:

    // C++ code
    extern "C" void f(int); // one way
    extern "C" {    // another way
        int g(double);
        double h();
    };
    void code(int i, double d)
    {
        f(i);
        int ii = g(d);
        double dd = h();
        // ...
    }

函数的定义如下:

 	/* C code: */
    void f(int i)
    {
        /* ... */
    }
    int g(double d)
    {
        /* ... */
    }
    double h()
    {
        /* ... */
    }

请注意,使用的是 C++ 类型规则,而不是 C 规则。因此,您不能使用调用声明为 extern “C” 函数的错误数量的参数。例如:

	// C++ code
    void more_code(int i, double d)
    {
        double dd = h(i,d); // error: unexpected arguments
        // ...
    }

3.如何从 C 调用 C++ 函数?

只需声明c++函数extern “C”(在你的c++代码中)并调用它(从你的C或c++代码中)。例如:

    // C++ code:
    extern "C" void f(int);
    void f(int i)
    {
        // ...
    }

现在 f() 可以这样使用:


/* C code: */
void f(int);
void cc(int i)
{
    f(i);
    /* ... */
}

当然,这仅适用于非成员函数。如果要从 C 调用成员函数(包括虚函数),则需要提供一个简单的包装器。例如:

// C++ code:
class C {
    // ...
    virtual double f(int);
};
extern "C" double call_C_f(C* p, int i) // wrapper function
{
    return p->f(i);
}

现在 C::f() 可以这样使用:

    /* C code: */
    double call_C_f(struct C* p, int i);
    void ccc(struct C* p, int i)
    {
        double d = call_C_f(p,i);
        /* ... */
    }

如果要从 C 调用重载函数,则必须为要使用的 C 代码提供具有不同名称的包装器。例如:

    // C++ code:
    void f(int);
    void f(double);
    extern "C" void f_i(int i) { f(i); }
    extern "C" void f_d(double d) { f(d); }

现在可以像这样使用 f() 函数:

 /* C code: */
    void f_i(int);
    void f_d(double);
    void cccc(int i,double d)
    {
        f_i(i);
        f_d(d);
        /* ... */
    }

请注意,即使您不能(或不想)修改 C++ 头文件,这些技术也可用于从 C 代码调用 C++ 库。

4.如何在 C++ 代码中包含标准 C 头文件?

要#include 标准头文件(例如 ),您不必做任何不寻常的事情。例如:

// This is C++ code
#include <cstdio>                // Nothing unusual in #include line
int main()
{
  std::printf("Hello world\n");  // Nothing unusual in the call either
  // ...
}

如果您来自 C,则 std::printf() 调用的 std:: 部分可能看起来不寻常,但这是用 C++ 编写的正确方法。
如果您正在使用c++编译器编译C代码,您不希望将所有这些调用从printf()调整为std::printf()。幸运的是,在这种情况下,C代码将使用老式的头文件<stdio.h>,而不是新式的头文件,命名空间的魔力将会处理好其他一切:

/* This is C code that I'm compiling using a C++ compiler */
#include <stdio.h>          /* Nothing unusual in #include line */
int main()
{
  printf("Hello world\n");  /* Nothing unusual in the call either */
  // ...
}

最后评论:如果您有不属于标准库的 C 头文件,我们为您提供了一些不同的指南。有两种情况:要么你不能改变头文件,要么你可以改变头文件

5.如何在 C++ 代码中包含非系统 C 头文件?

如果要包含系统未提供的 C 头文件,则可能需要将 #include 行包装在 extern “C” { // } 构造中。这告诉 C++ 编译器在头文件中声明的函数是 C 函数。

// This is C++ code
extern "C" {
  // Get declaration for f(int i, char c, float x)
  #include "my-C-code.h"
}
int main()
{
  f(7, 'x', 3.14);   // Note: nothing unusual in the call
  // ...
}

注意:有些不同的准则适用于系统提供的 C 头文件(例如 )和您可以更改的 C 头文件

6.如何修改我自己的 C 头文件,以便在 C++ 代码中更容易地#include 它们?

如果你包含的C头文件不是系统提供的,如果你能够改变C头文件,你应该强烈考虑添加extern“C”{…},以便c++用户更容易在他们的c++代码中使用#include。由于C编译器无法理解extern "C"结构,所以必须将extern “C”{和}行包装在#ifdef中,这样普通C编译器就不会看到它们。
第 1 步:将以下几行放在 C 头文件的最顶部(注意:当/仅当编译器是c++编译器时,符号__cplusplus是#defined)

#ifdef __cplusplus
extern "C" {
#endif

第 2 步:将以下几行放在 C 头文件的最底部:

#ifdef __cplusplus
}
#endif

现在你可以在你的 C++ 代码中#include 你的 C 头文件,而没有任何 extern “C” 废话:

// This is C++ code
// Get declaration for f(int i, char c, float x)
#include "my-C-code.h"   // Note: nothing unusual in #include line
int main()
{
  f(7, 'x', 3.14);       // Note: nothing unusual in the call
  // ...
}

注意:有些不同的准则适用于系统提供的 C 头文件(例如 )和您无法更改的 C 头文件

7.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);   // Note: nothing unusual in the call
  // ...
}

8.C 代码如何调用 C++ 函数 f(int,char,float)?

f(int,char,float)将由C编译器使用extern "C"构造来调用:

// This is C++ code
// Declare f(int,char,float) using extern "C":
extern "C" void f(int i, char c, float x);
// ...
// Define f(int,char,float) in some C++ module:
void f(int i, char c, float x)
{
  // ...
}

extern “C” 行告诉编译器发送给链接器的外部信息应该使用 C 调用约定和名称修饰(例如,前面有一个下划线)。由于 C 不支持名称重载,因此您无法让 C 程序同时调用多个重载函数。

9.当使用C(C++)调用C++(C)代码,连接器为什么会报错?

如果你没有得到正确的extern“C”,有时你会得到链接器错误而不是编译器错误。这是由于与C编译器不同,c++编译器通常“修改”函数名(例如,支持函数重载)。

10.如何将c++类的对象传递给给C函数,或者将C函数传递给C++类对象?

这是一个示例(有关 extern “C” 的信息,请参阅前两个常见问题解答)。
Fred.h:

/* This header can be read by both C and C++ compilers */
#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:

// This is C++ code
#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:

// This is C++ code
#include "Fred.h"
int main()
{
  Fred fred;
  c_function(&fred);
  // ...
}

c-function.c:

/* This is C code */
#include "Fred.h"
void c_function(Fred* fred)
{
  cplusplus_callback_function(fred);
}

与c++代码不同,C代码无法判断两个指针指向同一个对象,除非指针的类型完全相同。例如,在c++中,很容易检查一个名为dp的Derived是否指向一个名为bp的Base所指向的相同对象:只需说if (dp == bp) …c++编译器自动将这两个指针转换为相同类型,在本例中是Base*,然后进行比较。根据c++编译器的实现细节,这种转换有时会改变指针值的位。

技术旁白:大多数 C++ 编译器使用二进制对象布局,导致这种转换发生在多重继承和/或虚拟继承中。然而,C++ 语言并没有强加这种对象布局,因此原则上即使使用非虚拟单继承也可能发生转换。

指针很简单:您的 C 编译器不知道如何进行指针转换,因此例如从 Derived* 到 Base* 的转换必须在用 C++ 编译器编译的代码中进行,而不是在用 C 编译器编译的代码中进行。

注意:将两者都转换为 void* 时必须特别小心,因为这种转换不允许 C 或 C++ 编译器进行正确的指针调整!即使 (b == d) 为真,比较 (x == y) 也可能为假:

void f(Base* b, Derived* d)
{
  if (b == d) {   // Validly compares a Base* to a Derived*
    // ...
  }
  void* x = b;
  void* y = d;
  if (x == y) {   // BAD FORM! DO NOT DO THIS!
    // ...
  }
}

如果你真的想使用 void* 指针,这里是安全的方法:

void f(Base* b, Derived* d)
{
  void* x = b;
  void* y = static_cast<Base*>(d);  // If conversion is needed, it will happen in the static_cast<>
  if (x == y) {   // Validly compares a Base* to a Derived*
    // ...
  }
}

11.我的 C 函数可以直接访问 C++ 类对象中的数据吗?

有时。
你可以安全地从C函数访问c++对象的数据,如果c++类:

  • 1.没有虚函数(包括继承的虚函数)
  • 2.将其所有数据放在同一访问级别部分(私有/受保护/公共)
  • 3.没有完全包含虚函数的子对象

如果c++类有任何基类(或者任何完全包含的子对象都有基类),访问数据在技术上是不可移植的,因为继承下的类布局不是由语言强加的。但是在实践中,所有c++编译器都采用相同的方法:基类对象首先出现(在多重继承的情况下按从左到右的顺序出现),然后是成员对象。

此外,如果类(或任何基类)包含任何虚函数,几乎所有c++编译器都会在对象的第一个虚函数位置或对象的最开始处放入void*。同样,这不是语言所要求的,但这是“每个人”都这样做的方式。

如果类有任何虚基类,那么它就更加复杂,可移植性也更差。一种常见的实现技术是让对象最后包含虚拟基类(V)的对象(不管V在继承层次结构中的哪个位置显示为虚拟基类)。物体的其余部分按照正常顺序出现。每个以V作为虚基类的派生类实际上都有一个指向final对象中V部分的指针。

参考目录

https://isocpp.org/wiki/faq/mixing-c-and-cpp

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值