菜鸟思维思考 extern关键字

思考前提:VS2005
可以说,从来没有真正的用过extern,真正的思考过extern。不过现在不行了,因为要和C语言打真正的交道了。
在C工程中,这文件包含的关系处理可谓是一门高深的学问,因为它决定了编译器的执行行进路径。先来看一个纯C小工程。
/*a.c*/
#include "b.h"
void print0(void);
int main(int argc,char** argv)
{
	g_ma = 1;
	printf("%d\n",g_ma);
	print0();
	system("pause");
	return 0;
}
这个文件中首先声明了一个函数print0(),然后在main()中调用它。同时,也调用(赋值)了全局变量g_ma。
/*b.h*/
#ifndef _B_H_
#define _B_H_
#include <stdio.h>
#include <stdlib.h>
int g_ma;
#endif 
b.h文件中就定义了一个全局变量g_ma。
/*b.c*/
#include "b.h"
void print0(void)
{
	printf("hello world\n");
}
在b.c中,定义(实现)了函数print0()。
运行结果:顺利显示期望的数值。
变种1:把a.c中的void print0(void);改为extern void print0(void); 
运行结果:顺利显示期望的数值。
原因:extern关键字啥意思?就是声明的意思,这与void print0(void);这句的含义不谋而合。所以有无都没关系。
进一步:如果把这句全去掉,那么会报错,因为在b.h中和a.c中都没这个print0函数的声明,没有声明,编译器怎么知道它是啥呢。


变种2:在a.c中添加extern int g_ma;
#include "b.h"
void print0(void);
extern int g_ma;
int main(int argc,char** argv)
{
	g_ma = 1;
	printf("%d\n",g_ma);
	print0();
	system("pause");
	return 0;
}
运行结果:顺利显示期望的数值。
原因:extern int g_ma;只是告诉编译器有这么一个声明,在编译器发现它之前,编译器已经在#include "b.h"中发现了这个变量,所以有无都没关系。
进一步:如果去掉extern,肯定报错,因为int g_ma不是声明,而是定义,这样就和b.h中的重名了。


变种3:把b.h中的全局变量放到b.c中去,再在a.c中添加 extern int g_ma;
运行结果:这个地方就难理解了,其实不管是否有这个extern关键字,都会顺利显示期望的数值。
原因:先说加了extern的,编译器发现有这个声明后,知道,肯定在某个地方有定义。就像函数声明似的,知道有这个类型的函数定义存在。带到连接器执行的时候,再来最终调用g_ma。这不加extern就奇怪了,我打上二者的地址看,发现是一样的,修改其中一个变量的名字,地址就不一样了,猜想是编译器发现同样的变量名,就放在同一个静态地址,覆盖了。这是我理解不能的地方一


变种4:把函数的定义,变量的定义全放在b.h中,a.c中去掉全部声明。
#ifndef _B_H_
#define _B_H_
#include <stdio.h>
#include <stdlib.h>
int g_ma;
void print0(void)
{
	printf("hello world : %d\n",g_ma);
}
#endif 

#include "b.h"
int main(int argc,char** argv)
{
	g_ma = 1;
	printf("%d\n",g_ma);
	print0();
	system("pause");
	return 0;
}
运行结果:一开始想,肯定没问题啊,a.c中的陌生变量,函数都可以在b.h中找到啊。可是报print0()函数多重定义。这是我理解不能的地方二。
进一步:如果把a.c改为a.cpp,就可以通过了,看来c和c++的编译规则还是又很大区别的。


变种5:把a.c改为a.cpp,去掉所有声明,然后在b.h中添加print0()的声明。
#ifndef _B_H_
#define _B_H_
#include <stdio.h>
#include <stdlib.h>
int g_ma;
void print0(void);
#endif 

#include "b.h"
int main(int argc,char** argv)
{
	g_ma = 1;
	printf("%d\n",g_ma);
	print0();
	system("pause");
	return 0;
}
运行结果:报错,void __cdecl print0(void)" (?print0@@YAXXZ),该符号在函数 _main 中被引用。
原因:因为c和c++对编译后的函数名,变量名所采用的名字改编规则是不一样的,所以c处理的b.c中的print0和c++处理的print0是不一样的。解决方法是告诉编译器,对于print0要采用c规则的名字改变,所以要添加extern “C”在函数名字前,不过依然报错,因为extern “C”是c++的语法,所以要在c++文件中书写,所以最后就是把函数声明extern “C” 放在a.cpp文件中。
进一步:如果把全局变量g_ma也放在b.c文件中,也会报这个错误。
疑问:这b.h到底是怎么被编译的?
猜想:我们看到,这个b.h被a.cpp和b.c包含了,而一个是c++文件,另一个是c文件,虽然有防止重复包含的#ifndef语句,但鬼知道编译器在编译之前,会采用相应的规则去检查语法呢。所以这个b.h会被c++和c规则检查两遍,所以函数声明的摆放位置就显得格外重要了。


结论:如果在c或c++中采用文章一开始的文件包含顺序,我感觉extern关键字没啥作用,有无没关系。当c或c++混合编程的时候,这个关键字作用就体现了,不过它的作用是陪衬“C”
这个字符串。


菜鸟思维有限,谢谢观赏!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值