应用篇之extern“C”

基本概念

  C++的项目源码中,经常会看到下面的代码:

#ifdef __cplusplus
extern "C" {
    
    
#endif
 
/*...*/
 
#ifdef __cplusplus
}
#endif

  这里重点介绍extern “C”。在介绍extern "C"之前,简单说下在这里为什么需要#ifdef _cplusplus/#endif _cplusplus。因为C语言中不支持extern "C"声明,如果你明白extern "C"的作用就知道在C中也没有必要这样做。
  extern "C"的真实目的是实现类C和C++的混合编程。C ++函数上的extern“ C”属性关闭名字修饰(name mangling),以便publick或extern名称与C语言兼容。 这对于解决不兼容名字处理方案的问题可能很有用。extern“ C”属性仅适用于可以用C编码的函数。因此,重载函数和成员函数不能具有extern“ C”属性。
  根本原因是C和C++的编译和连接机制不一样。首先,看看C++的编译和连接。C++是一个面向对象语言(虽不是纯粹的面向对象语言),它支持函数的重载,重载这个特性给我们带来了很大的便利。为了支持函数重载的这个特性,C++编译器实际上将下面这些重载函数:

void print(int i);
void print(char c);
void print(float f);
void print(char* s);

编译为:

_print_int
_print_char
_print_float
_pirnt_string

  这样的函数名,来唯一标识每个函数(常说的mangledname)。注:不同的编译器实现可能不一样,但是都是利用这种机制。所以当连接是调用print(3)时,它会去查找_print_int(3)这样的函数。下面说个题外话,正是因为这点,重载被认为不是多态,多态是运行时动态绑定(“一种接口多种实现”),如果硬要认为重载是多态,它顶多是编译时“多态”。C++中的变量,编译也类似,如全局变量可能编译g_xx,类变量编译为c_xx等。连接是也是按照这种机制去查找相应的变量。
  然后,我们看看C的编译和连接。C语言中并没有重载和类这些特性,故并不像C++那样print(int i),会被编译为_print_int,而是直接编译为_print等。因此如果直接在C++中调用C的函数会失败,因为连接是调用C中的print(3)时,它会去找_print_int(3)。因此extern "C"的作用就体现出来了。

示例演示

  通过C和C++互相调用的例子,形象地理解extern “C”。

1、C++调用C

   首先我们建立一个C的工程。C的头文件cheader.h中包含一个函数print(int i),代码如下

#ifndef C_HEADER
#define C_HEADER

extern void print(int i);

#endif C_HEADER

  头文件对应的源文件为cheader.c,代码如下

#include "cheader.h"
#include <stdio.h>

void print(int i)
{
    
    
	printf("c header %d\n", i);
}

  main函数在main.c文件实现,代码如下。注意这里后缀为.c,所以是C工程。

#include <stdlib.h> 
#include "cheader.h"

int main(int argc, char** argv)
{
    
    
	print(3);
	system("pause");
	return 0;
}

  当然这里运行没有任何问题。要想在C++中调用C,我们可以main.c文件的名字改为main.cpp,仅仅改变后缀名。然后发现编译错误,报错内容如下:

LNK2019  无法解析的外部符号 "void __cdecl print(int)" (?print@@YAXH@Z),
该符号在函数 main 中被引用

  这时候我们在main.cpp里加上extern "C"就可以编译通过,并运行成功。

#include <stdlib.h> 

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

int main(int argc, char** argv)
{
    
    
	print(3);
	system("pause");
	return 0;
}

2、C调用C++

  首先我们建立一个C++的工程。C++的头文件cppheader.h中包含一个函数print(int i),代码如下

#ifndef CPP_HEADER
#define CPP_HEADER

void print(int i);

#endif CPP_HEADER

  头文件对应的源文件为cppheader.cpp,代码如下

#include "cppheader.h"
#include <iostream>
using namespace std;

void print(int i)
{
    
    
	cout << "cppHeader " << i << endl;
}

  main函数在main.cpp文件实现,代码如下。注意这里后缀为.cpp,所以是C++工程。

#include "stdlib.h"
#include "cppheader.h"

int main(int argc, char** argv)
{
    
    
	print(3);
	system("pause");
	return 0;
}

  当然这里运行没有任何问题。要想在C中调用C++,我们可以main.cpp文件的名字改为main.c,仅仅改变后缀名。然后发现编译错误,报错内容如下:

LNK2019	无法解析的外部符号 print,该符号在函数 main 中被引用	

  这时候我们现在cppheader.h文件里加上extern “C”。

#ifndef CPP_HEADER
#define CPP_HEADER

extern "C" void print(int i);

#endif CPP_HEADER

  由于cppheader.h头文件里添加了extern “C”,所以在main.c文件里不能包含该头文件。原因是编译前cppheader.h的全部内容会被拷贝到main.c里,由于C不支持extern “C”,编译肯定通不过。所以我们去掉cppheader.h头文件包含,添加print函数声明。代码如下:

#include "stdlib.h"
//#include "cppheader.h"

void print(int i);

int main(int argc, char** argv)
{
    
    
	print(3);
	system("pause");
	return 0;
}

  这样就无警告的编译通过,并运行成功。extern "C"的作用就是指定符合C语言的编译,编译器不修饰(mangle)名字。

参考资料

  • calling_conventions[M]

猜你喜欢

转载自blog.csdn.net/webzhuce/article/details/104683471
今日推荐