c/c++编程小知识/19.extern和C

这个流程我一直有疑问,其实是我对之前的编译过程理解的不好。

1. extern

这个关键字的作用就是声明该变量或函数来自于其它函数,让函数的编译过程能够成功执行。如果这个函数或变量从来都没出现过,加上extern也能让程序编译通过,但是链接的时候会报错,例如undefined reference to错误。

1.1 变量

在定义变量的时候,这个extern居然可以被省略(定义时,默认均省略);在声明变量的时候,这个extern必须添加在变量前,所以有时会让你搞不清楚到底是声明还是定义。或者说,变量前有extern不一定就是声明,而变量前无extern就只能是定义。注:定义要为变量分配内存空间;而声明不需要为变量分配内存空间。

extern int a; // 声明一个全局变量 a
int a; // 定义一个全局变量 a
extern int a =0 ; // 定义一个全局变量 a 并给初值。
int a =0;    // 定义一个全局变量 a, 并给初值,

第四个等于第三个,都是定义一个可以被外部使用的全局变量,并给初值。

总之,我在写的时候,定义是肯定不加extern的,只有声明的时候加,我看很多代码也都是这个亚子。

1.2 函数

函数,对于函数也一样,也是定义和声明,定义的时候用extern,说明这个函数是可以被外部引用的,声明的时候用extern说明这是一个声明。 但由于函数的定义和声明是有区别的,定义函数要有函数体,声明函数没有函数体(还有以分号结尾),所以函数定义和声明时都可以将extern省略掉,反正其他文件也是知道这个函数是在其他地方定义的,所以不加extern也行。两者如此不同,所以省略了extern也不会有问题。

2. extern C

2.1 问题的产生

这个是在项目中遇到的,大体的伪代码是酱婶的,就叫TAO.cpp吧,大爱冈本多绪。

#include "a.h"


#ifdef __cplusplus
extern "C" {
#endif 
    
extern void x(void);    

void y(void)
{
    x();
}
    
#ifdef __cplusplus
}
#endif

其中x()来自于.c,而这个y()也要被送到.c中调用,但是y()是在.cpp中定义的。结果这个函数就不停的报错,基本都是链接错误,例如这个就会报undefined reference to,如果把第8行的放到了第3行,也会报conflicts with new declaration with ‘C’ linkage,不加extern还是会报undefined reference to。我特么彻底懵逼了,曾经认为是我引用的a.h有问题,因为x这个函数是没有.h声明,所有的使用都是通过在源文件中加extern。我也尝试过创建TAO.h,把extern void x(void);放在那里,现在一想就是徒劳。

后来因为那个conflicts的报错,我尝试着新建了一个TAO.c,同样的代码引用x(),但是成功了,这足以说明是C++的问题,后来冒死在公司玩了手机,查的谷歌,在cplusplus上看到,可能是a.h的问题,我就把第一行的放到了第7行,结果一下子就过了。

2.2 extern C的秘密

这篇文章很好《再谈谈只针对C++编译器/链接器的extern “C”------C与C++的相互调用

这涉及到两个步骤,C++调用C和C调用C++,这也就是为啥上面的错误我之前没发生过。

2.2.1 C++ 调用C,也就是我上面问题产生的原因
// 注意: 该代码是C++程序, 请放在.cpp文件中, 这样确保是C++编译器
 
#include <stdio.h>
 
extern void fun(); // 暂时骗过C++编译器
 
int main()
{
	fun();
 
	return 0;
}

这个编译肯定没问题,但是链接指定会挂。

因为C++中有重载的关系,所有的函数都不是原名,在调用中也是,函数的名称会发生改变,在声明函数的时候,尤其是extern,将必然找不到函数,因此就可以用extern告诉编译器,在找函数的时候就用fun()这个名字找,别用别的。所以最终的结果代码改为


#include <stdio.h>
 
extern "C" void fun(); // 暂时骗过编译器, 并对链接器说, 你要按照C规范链接, 去找_fun, 而不是"?fun@@YAXXZ"
 
int main()
{
	fun();
 
	return 0;
}

extern "C"的作用之一是:在C++调用C的时候, 告诉C++编译器, 编译的时候, 不要因为看不到fun而伤心;链接的时候,不要用默认的C++规范查找, 而要采用C规范去查找。

2.2.2 C调用C++

其实原理是一样的,也是防止重载,因此在定义函数的时候,就直接定义在extern C中

// 注意: 该代码是C++程序, 请放在.cpp文件中, 这样确保是C++编译器
 
#include <stdio.h>
 
extern "C" void fun() // 按C编译器的方式去编译,要生成_fun, 而不是"?fun@@YAXXZ"
{
	printf("ok\n");
}
// 注意: 该代码是C程序, 请放在.c文件中, 这样确保是C编译器
 
#include <stdio.h>
 
extern void fun(); // C编译器不严格,本可以去掉本句,但为了移植性, 最好不要去掉
 
int main()
{
	fun(); // C编译器会去找_fun
 
	return 0;
}

当然,也可以采用我上面的那种用大括号把整个c函数全部包裹住。

extern "C"的作用之二是:在C调用C++的时候, 告诉C++编译器, 编译的时候, 不要按照C++默认的规范去编译, 而应该按照C规范去编译。

3. extern和头文件

3.1 编译过程

那涉及到多个文件的编译过程呢?

我原来一直认为用头文件的方式是唯一的,然而不是,只不过用头文件是最好的方式而已。以下的说法我在网上看到的,但是我自己亲测不可用,可能是我中间哪个环节有问题,或者说他的实验用的不好。

https://blog.csdn.net/qq_16542775/article/details/81353841

头文件的话,源文件在调用的时候是不用加extern的,因为在头文件中会自己加入。如果没有头文件也可以,但是需要在被调用的地方加入extern

例如test.c中有个函数func(),test.h中声明extern func(),main.c中如果需要调用func(),可以调用test.h,这样main.c中就可以直接用。

再例如(这个我失败了,但是网上说的成功了):test.c中定义func(),没有test.h,main.c中加上extern func(),直接在main中使用。

3.2 问答

(a)用#include可以包含其他头文件中变量、函数的声明,为什么还要extern关键字?

(b)如果我想引用一个全局变量或函数a,我只要直接在源文件中包含#include (xxx.h包含了a的声明)不就可以了么,为什么还要用extern呢??

答案:如果一个文件(假设文件名A)要大量引用另一个文件(假设文件名B)中定义的变量或函数,则使用头文件效率更高,程序结构也更规范。其他文件(例如文件名C、D等)要引用文件名B中定义的变量或函数,则只需用#include包含文件B对应的头文件(当然,这个头文件只有对变量或函数的声明,绝不能有定义)即可。

那是一个被遗忘的年代,那时,编译器只认识.c(或.cpp)文件,而不知道.h是何物的年代。

那时的人们写了很多的.c(或.cpp)文件,渐渐地,人们发现在很多.c(或.cpp)文件中的声明变量或函数原型是相同的,但他们却不得不一个字一个字地重复地将这些内容敲入每个.c(或.cpp)文件。但更为恐怖的是,当其中一个声明有变更时,就需要检查所有的.c(或.cpp)文件,并修改其中的声明,啊~,简直是世界末日降临!

终于,有人(或许是一些人)再不能忍受这样的折磨,他(们)将重复的部分提取出来,放在一个新文件里,然后在需要的.c(或.cpp)文件中敲入#include XXXX这样的语句。这样即使某个声明发生了变更,也再不需要到处寻找与修改了—世界还是那么美好!

因为这个新文件,经常被放在.c(或.cpp)文件的头部,所以就给它起名叫做"头文件",扩展名是.h。

从此,编译器(其实是其中预处理器)就知道世上除了.c(或.cpp)文件,还有个.h的文件,以及一个叫做#include命令。

以上内容,有参考或复制于

https://www.runoob.com/w3cnote/extern-head-h-different.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章