extern "C" 含义 以及函数声明的重要性

extern “C”

extern “C” 的作用是让 C++ 编译器将 extern “C” 声明的代码当作 C 语言代码处理,可以避免 C++ 因符号修饰导致代码不能和C语言库中的符号进行链接的问题。

代码如下

#ifndef __XXX_H__
#define __XXX_H__
#ifdef __cplusplus // __cplusplus是C++的预定义宏,表示当前编译环境是C++
extern "C" {//告诉编译器下面的函数是c语言函数(因为c++和c语言对函数的编译转换不一样,主要是c++中存在函数重载,而c语言没有
#endif
/*...*/
#ifdef __cplusplus
};
#endif

在C++中因为存在函数的重载,所以在生成的汇编代码中,会对函数名做出一些处理,要能表示函数的参数类型和返回值。而在c语言中表示一个函数,只是使用函数名而已

int fun(double a);
int fun(int a);

c语言没有办法区分上面两个函数,因为c语言编译该函数之后产生的函数名都是_fun,而c++编译之后产生的可能是是_fun_Fd和_fun_Fi,c++能很好的区分这两个函数(不同编译器产生函数名的规则可能不同)。
在c++中要使用c语言,就需要使用extern “C”,告诉编译器这段代码使用的标准是c,这样产生函数名的时候也就是按照c语言的标准进行产生,这样在c++中才能正确的使用c语言的动态链接库,如果按照c++的标准来进行编译,链接的时候因为函数名不同,在库函数中是找不到对应的实现的。
因为在C++出现以前,很多代码都是C语言写的,而且很底层的库也是C语言写的,为了更好的支持原来的C代码和已经写好的C语言库,需要在C++中尽可能的支持C,而extern "C"就是其中的一个策略。

我们来测试一下

假设这段代码就是我们要在c++中调用的c语言代码

文件名:lib.c

#include
void hello(){
        printf("A section of c language code\n");
}

文件名:lib.h

void hello();

我们将其编译生成了目标文件,然后我们想在c++使用这段代码的目标文件

文件名:hello.cpp

#include
#include "l.h"

int main(){
        hello();
        return 0;
}

使用命令来进行编译

# lib.o是lib.c编译之后的目标文件
g++ hello.cpp lib.o

出现下面这样的错误,为什么?我们都引入了头文件和目标文件了
在这里插入图片描述
这种错误,是一种链接错误,是在链接的过程中找不到目标而出现的错误
我们在利用一下命令来生成这段代码的汇编看看到底是为什么

# 生成汇编只是一个编译过程,还没有进行链接,所以不会出现上面的错误
g++ -S hello.cpp

查看图片的划线部分,发现call的函数名为_Z5hellov,而我们使用gcc编译出现的lib.c的目标文件lib.o,其中肯定没有这个函数定义,所以最终出现上面的错误。
extern
我们修改一下上面的c++代码

#include
extern "C"{
	#include "l.h"
}
int main(){
        hello();
        return 0;
}

这时候使用下面命令来编译肯定不会出错了,为什么?我们依旧生成汇编来看看

g++ hello.cpp lib.o

相同的位置,可以发现,call的函数名为hello,这就是按照c语言标准生成的,所以最终也能在目标文件中找到函数定义,并且链接成功
extern

c语言中函数声明的作用

在c语言中函数声明不是必须的,即使没有声明函数,gcc编译器也只是会提示警告,但是函数声明却是很有必要的,在c++中是严格要求必须要有函数声明,你调用的每一个函数,都必须要有其函数声明,否则就会出现下面这样的错误。
在这里插入图片描述
那么函数声明到底有声明作用呢?
其实函数声明的作用是让编译器帮你检查你调用函数时有没有错误。比如参数的数量是否正确,如果调用函数时候少传入一个参数,并且没有声明该函数,编译器无法知道你调用是否正确,只会提示一个警告,很多人会忽略警告,导
致最后程序运行时出现异常。
c语言因为不支持重载,只是根据函数名称来决定调用哪个函数(就像的c程序编译之后的函数调用,就是根据函数名来调用的),也就是说,可调用的函数定义只会有一个(有多个同名的函数的话会报错),所以可以没有函数声明。你有了函数声明他可以根据变量的类型来判断你这个函数调用是否正确,帮你进行错误检测。
c++的情况要复杂一点,因为其支持重载,同一个名称的函数可能有很多个,所以他不能根据函数名称来决定调用哪个函数定义,所以c++会严格要求必须要有函数声明,编译时才能根据函数声明来决定到底调用的哪个函数定义。我们从上面看出c++生成的汇编call后面函数名不是你写的函数名,因为函数名不能唯一确定函数定义,需要根据函数声明来唯一确认你这个call到底调用的哪个函数

为什么如果不声明函数,编译器发现不了错误?
编译器在编译过程中依次生成对应源文件的可重定位目标文件(.o),每个源文件中调用的函数在链接前都是以符号的形式体现在.o文件中,也就是说,我们可以把每一个.c文件的编译过程看成一个独立的过程。一个A.c文件调用了另外一个B.c文件的函数,如果没有函数声明,在编译的时候,A.c中的调用怎么知道B.c中的这个函数需要什么参数,当然也就不能进行错误检测了。有了函数声明,编译器就能根据函数声明了标识符的类型来判断你的调用是否正确了。

你可能感兴趣的:(c语言,c++)