关于 c 语言中 extern 关键字的一些疑问

2020-01-07 22:09:22 +08:00
 NGPONG

c 中的 extern 能够使一个变量的声明的作用域提升到全局

比如说我在 main.c 的 main 函数中声明了 两个变量 int a,b; 而这两个变量在另一个源文件中 test.c 有其定义:int a = 10;int b =30; 这时候如果我要在 main.c 的 main 函数中单纯以 int a,b; 的声明方式是无法使用这两个变量的(未定义),除非加了 extern

这个是 extern 的一种用法

但是假设我这个 int a,b; 并不是声名在 main.c 的 main 函数内部,而是 外部,那我还是照样可以拿到在 test.c 所定义的 a 和 b 啊,在这种情况我加不加 extern 有啥区别

talk cheap,show me the code...

情景一: main.c 中不使用 extern 声明外部变量

******main.c
#include <stdio.h>
int a;
int main(void) {
	
	a = 40;

	extern int a;

	printf("%d\n", a);

	system("pause");
	return EXIT_SUCCESS;
}


******test.c
int a = 66666;




******result: 66666

情景一: main.c 中使用 extern 声明外部变量

******main.c
#include <stdio.h>
extern int a;
int main(void) {
	
	a = 40;

	extern int a;

	printf("%d\n", a);

	system("pause");
	return EXIT_SUCCESS;
}


******test.c
int a = 66666;




******result: 66666
2657 次点击
所在节点    程序员
15 条回复
thedrwu
2020-01-07 22:24:05 +08:00
extern 是给 linker 看的 。
总共就一个文件时,任凭你怎么试结果都一样。
ipwx
2020-01-07 22:32:57 +08:00
你需要学习一下 c 语言的编译原理
lcdtyph
2020-01-07 22:47:39 +08:00
无论怎么样情形二也得输出 40 才对吧
constexpr
2020-01-07 22:48:28 +08:00
extern 这里的作用是链接,不是为了什么作用域提升。你的第一段代码在 CPP 中是违反 ord 的, 在 c 语言中应该是未定义的行为
wutiantong
2020-01-07 23:42:14 +08:00
@constexpr 这名字很 cpp
fengtons
2020-01-07 23:54:40 +08:00
第一段代码 main 外面的 int a 和里面的 extern int a 是两个不同的变量,作用域不同。
Edcwsyh
2020-01-08 00:11:42 +08:00
如果在 main.c 文件 extern int a,那么这个时候变量 a 和 test.c 中的变量 a 是同一段内存
如果去掉 extern,那么这个时候相当于重新定义了一个变量 a,这个变量 a 和 test.c 的变量 a 不是同一段内存,这个时候在编译的时候不会报错,但是在链接的时候有出现“重定义”的情况(名称为 a 的全局变量总共有两个)
awenxjtu
2020-01-08 08:19:03 +08:00
c 语言是按源码文件单独编译的。在编译一个源文件时编译器会检查所有用到的变量是否都有定义。当源本文件中需要用到其它源文件中定义的变量是,需要在本源文件中用 extern 声明一下,这是告诉编译器这个名字的变量不在本源文件定义,您老发现没定义也别报错。
awenxjtu
2020-01-08 08:37:11 +08:00
很多朋友认为 extern 影响链接,这样就理解偏了。c 语言可以定义一个变量,比如 int a;这样编译器就会编译时分配一个此类型的内存。在一个代码作用域中如果有两个同名变量时编译器会分不清楚该使用哪个,会抛出重复定义的错误。c 语言还可以只声明有一个变量,比如 extern int a; 这样编译器就知道在某个其它源文件中有这么一个变量,在这个源文件中不会分配这个变量的内存。但是编译器不保证 extern 的变量一定存在且唯一,这个是连接器来检查的,连接器负责把所有源文件由编译器编译后的输出物合并成一个二进制,如果对于一个全局变量如果所有源文件都没有定义就会报符号未定义的错误,如果有多个源文件都有定义就会报重复定义的错误,只有所有源文件中只有一处定义才能正常连接成功,连接器这个流程和 extern 也没什么关系。extern 是一个语法标识符,在编译阶段就处理掉了。
oahebky
2020-01-08 08:57:11 +08:00
"作用域提升到全局" -- 建议不是必须的话还是别学了。

C 语言对你可能太原始了。特别是 C 语言并不像其它高级语言中的高级语言那么多培训视频可以看。
4D725F646F6765
2020-01-08 09:43:39 +08:00
首先,C 语言编译的时候是支持多文件编译的,但是各个文件变量都是在源代码文件内有效,可是有时候需要在不同文件使用相同的变量,这时候就需要通过别的方式告诉编译器了,extern 声明是给编译器看的,这个声明的作用就是告诉编译器这个变量是来自别的文件(代码段),让编译器去别的源代码文件找,另外如果在别的文件声明过这个变量,然后使用 extern 声明来使用该变量,本文件内的变量就不应该出现重名变量,这是在给编译器找麻烦,给编译器找麻烦==给自己找麻烦

C 语言的代码作用域都是在编译的时候就确定的,编译完成后就结束了,所以并不存在"作用域提升"的现象,编译的时候变量属于哪里作用域就会默认在哪里,想区域就声明在代码块内,想全局就声明在文件主函数外,想从别的文件借就 extern 声明,所有变量在编译完成后都是一环扣一环对应代码执行的,牵一发动全身,根本无法进行作用域提升这种事,作用域编译时就会被确定完毕,比如说函数参数,a 函数传参给 b 函数,那么编译器在汇编 a 代码时,到调用 b 函数处,会把参数放在固定位置的寄存器,比如 r12,然后 b 函数汇编就会从该寄存器取参,b 函数完成后,会把返回值放入 rax,然后返回 a 函数代码断点,a 函数会从 rax 取返回值,继续执行

所以说,在这里实际上 "变量作用域" 和 "编译附加指令",其实并不是一回事,LZ 的理解错误了
koebehshian
2020-01-08 09:56:46 +08:00
第 2 段代码我用 MinGW 编译,链接时找不到变量 a
soli
2020-01-08 10:02:04 +08:00
第二种情景,我这里输出是 40。

Apple clang version 11.0.0 (clang-1100.0.33.16)
Target: x86_64-apple-darwin19.2.0
bp0
2020-01-08 10:12:41 +08:00
第一种情况,你给 int a ;随便赋个值,最后连接阶段应该会报错的。

当连接器发现 int a ;和 int a = 66666 ;同时出现时,会使用后者。具体的标准名称记不住了,前面的会被当成弱定义,
bp0
2020-01-08 10:15:42 +08:00
第一种情况,你给 int a ;随便赋个值,最后连接阶段应该会报错的。

当连接器发现 int a ;和 int a = 66666 ;同时出现时,会使用后者。前面的会被当成弱定义,后面的当成强定义。所以选用后者。(只记住强弱,具体的标准名称记不住了)

至于为什么没有被修改为 40,应该是因为编译器给内部定义的 int a 分配了地址,前面的 a = 40;是对这个编译器分配的地址进行赋值的。而后面的打印时,编译器表示 a 是外部符号,请求连接器分配地址。所以打印出 66666.


第二种情况,应该是输出 40。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/635932

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX