与C/C++关键字extern有关的原理
关键字有一定的语义,但是用法不唯一。
对于C/C++语言的预编译、编译、汇编、链接。我相信大家在接触C++一年不到就背的滚瓜烂熟,但是其中的细节,是后来才慢慢想明白的。为什么我不讲extern关键字呢?extern关键字的渊源深着呢,耐心学完前面的内容,extern的神秘面纱自然就解开了。
众所周知,C语言的出现先于C++,而汇编语言的出现又先于C。但是不管你用它们中任何一门语言编写程序。编译后都生成一个可执行的程序(前提是代码没有语法错误)。
对于使用汇编写好的程序,我们只需要把汇编源代码交给汇编器,汇编器就输出可执行文件给我们。
对于C语言写好的程序,我们把C源代码交给预处理器,预处理器把C源代码中的宏“消化”掉,生成纯C源代码,然后把纯C代码交给编译器,编译器输出相应的汇编代码,之后的处理方式就跟汇编写的一样了,交给汇编器,汇编代码与最后的可执行代码可以说是一一对应的。使用C比使用汇编方便,因为编译器为我们做了很多固定的事情,但是前提是你必须明白编译器的原理,不然就乱套了。
随着软件规模的发展,后来的人们发现 有的软件功能太多了,全部放在一个main.c文件中真的太大了,逻辑混乱,不好读。另外还有更重要的一点,在A程序中写过的一个功能,在B程序中又要使用同样的功能,需要去A的源代码里面找到,复制过来,复制错了还麻烦。所以出现了一个重要的概念,“多文件编译”:每一个.c文件经过一个编译流程都能生成一个目标文件。即使.c文件没有main函数,也能生成一个目标文件。这里我们约定,没有main函数的.c文件编译出来的叫做目标文件,含有main函数的.c文件编译出来的叫做待链接可执行文件。一个目标文件可以调用其他任何一个目标文件中定义的函数代码、变量,前提是另外一个目标文件“允许”,如何允许或者拒绝呢?这里就靠我们使用extern和static、auto这类关键词控制了,如何控制我们之后再细谈。
链接器就是这个时候产生的,一个待链接可执行文件+n个相关的目标文件=可执行文件,这里的n可以等于0。这样就形成了多文件编译,虽然过程复杂了点,但是把一个main.c分割成多个源文件,解决了上面说到的两个严重问题。当然你不喜欢链接也可以不链接,把所有内容写在main.c里就不需要链接过程了。我们想要编写应用程序时,第一件事就是创建一个.c文件,在里面写一个main函数,然后开始编写main函数。我们在main函数中可以调用一些其他的函数,这些函数可以不跟main函数在一个源文件里,注意,不在同一个源文件也就意味着不在同一个目标文件里了,不在同一个目标文件,就需要跨目标文件调用,这是链接器的工作,我们只需要把main函数可能用到的所有目标文件丢给链接器,链接器会根据函数名和调用关系把他们链接生成一个完整的可执行文件。所有的目标文件都可以互相调用,但是我们在编写程序时,大多数这个调用关系是单向的,最后汇总到main函数。如果一个函数(不管在哪个文件里)没有被main函数直接或者间接的使用,那他就没有被链接的必要,链接器在链接的时候就不把他链接进可执行文件里。
多文件编译 全靠 链接器 支持,链接器怎么这么神奇?它为什么知道要怎么链接?哈哈,其实链接器并没有你想的那么神奇,它并不是凭空就把目标文件链接起来了。在它链接之前,我们需要给它留下一些记号、给它一些提示。要是提示的不明显、有二义性,它就立即报错让你链接失败,这一点想必经历过多文件编译的人都深有体会。有语法错误编译器会提示我语法错误在第几行。但是有时明明没有语法错误,链接器还给我报满满一屏幕的编译错误,而且还不告诉我在第几行,这个就是链接错误。链接错误很简单,所有的链接错误都是链接器在说:根据你给我留下的提示,我找不到你想要调用的函数在哪,或者我找到了好多个可行选择,不知道选哪个。。。
接下来重点来了,我告诉你如何提示链接器正确的链接。接下来我会用到3个关键词extern,static,auto。
纯C语言源代码由全局函数和全局变量组成(局部变量总是包含在全局函数里,算是全局函数的子集,就不作数了),对于函数一共有这3种写法:int fun(); static int fun(); extern int fun()。对于变量一共有4种写法:int val; auto int val; static int val; extern int val;
额,为什么不对称呢,函数为什么不能auto int fun();呢?
接下来我说几个C链接器的游戏规则:
写int fun();和写extern int fun();是一样的意思。意思是:这个函数可以让其他目标文件调用。而static int fun();的意思相反,不让其他目标文件看到这个函数,让他们在链接时找不到,只有在本文件内才能调用static修饰的函数。对于函数,就这么两个规则。
对于全局变量,写int val;和auto int val;意思一样,都是让其他源文件可以访问。而写static int val;意思就是只能在本文件内部使用。写extern int val;的意思就是:val变量不是在这里产生的,而是其他文件定义的,我想调用它,事先声明一下,这就叫做扩展声明。而函数就不需要扩展声明,因为没有函数体的函数都是函数声明,所以不需要留有关键词做提示。
现在大概明白这三个关键词的语义了吧。static最直当明了,沾到它别的文件就别想访问了。而auto几乎没什么用,几乎没人会在这里用auto(c++11中auto关键词可以用来让编译器自动推导出类型,使代码简洁)。
再后来,有了C++这门语言,编译器为我们做了更多的事情,比C编译器做的还多。最简单的,重载函数,为了支持重载函数,编译器会在编译时为它们重命名。这时如果你不知道C++的游戏规则,可能又会使链接器找不到链接对象了。
与C/C++关键字extern有关的原理的更多相关文章
- C# 关键字extern用法
修饰符用于声明在外部实现的方法.extern 修饰符的常见用法是在使用 Interop 服务调入非 托管代码时与 DllImport 属性一起使用:在这种情况下,该方法还必须声明为 static,如下 ...
- 【转载】理解C语言中的关键字extern
原文:理解C语言中的关键字extern 最近写了一段C程序,编译时出现变量重复定义的错误,自己查看没发现错误.使用Google发现,自己对extern理解不透彻,我搜到了这篇文章,写得不错.我拙劣的翻 ...
- volatile关键字的作用、原理
在只有双重检查锁,没有volatile的懒加载单例模式中,由于指令重排序的问题,我确实不会拿到两个不同的单例了,但我会拿到"半个"单例. 而发挥神奇作用的volatile,可以当之 ...
- 【校招面试 之 C/C++】第29题 C/C++ 关键字extern
1.extern "C" extern "C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码.加上extern "C"后,会指示 ...
- 面试题:volatile关键字的作用、原理
在只有双重检查锁,没有volatile的懒加载单例模式中,由于指令重排序的问题,我确实不会拿到两个不同的单例了,但我会拿到“半个”单例. 而发挥神奇作用的volatile,可以当之无愧的被称为Java ...
- C/C++关键字 extern
1.基本解释:extern 可置于变量或函数前面,表示变量或函数的定义在别的文件中,以提示编译器遇到此变量或函数时在其他模块中寻找定义. extern还有另外2个作用.第一:与“C”连用时,如 ext ...
- 关键字 extern
定义:extern可置于变量或者函数前,以表示变量或者函数的定义在别的文件中.编译器会到其他模块中寻找其定义. extern int f(); extern int i; extern关键字 作为 ...
- 18.21 关键字extern
用#include可以包含其他头文件中变量.函数的声明,为什么还要extern关键字? 1.头文件 其实头文件对计算机而言没什么作用,只是在预编译时在#include的地方展开一下,没别的意义了.将头 ...
- Java的 volatile关键字的底层实现原理
我们知道volatile关键字的作用是保证变量在多线程之间的可见性,它是java.util.concurrent包的核心,没有volatile就没有这么多的并发类给我们使用.本文详细解读一下volat ...
随机推荐
- windows同时安装了两种jdk
因为某种需要,同时安装jdk1.8 和jdk1.7 因为电脑曾经安装了jdk1.8,并且已经设置好环境变量.后来新添加的jdk1.7 出现问题: 先是安装的1.8,之后有安装了1.7,在环境变量中也配 ...
- js实现千位分隔
最近一个项目中使用到了千位分隔这个功能,在网上也看见一些例子,但是实现起来总觉有些复杂.因此,自己实现了一个千位分隔,留给后来的我们. 先上源码吧. 该方法支持传入的是一个数字字符串,数字.第二个参数 ...
- 20175317 《Java程序设计》第一周学习总结
20175317 <Java程序设计>第一周学习总结 教材学习内容总结 本周学习了Java大致的开发步骤,完成了课件自带的习题. 学习了在windows与Linux系统下不同的编译方法,掌 ...
- spring 配置Value常量(不支持到static上)
spring 配置Value常量(不支持到static上) 看代码吧,语言表达有问题. package com.variflight.xzair.rest.constant; import org.s ...
- node模块之events模块
events 模块只提供了一个对象: events.EventEmitter. [EventEmitter 的核心就是事件触发与事件监听器功能的封装.] EventEmitter 的每个事件由一个事件 ...
- hdu多校第3场C. Dynamic Graph Matching
Problem C. Dynamic Graph Matching Time Limit: / MS (Java/Others) Memory Limit: / K (Java/Others) Tot ...
- Beta 冲刺 (4/7)
Beta 冲刺 (4/7) 队名:第三视角 组长博客链接 本次作业链接 团队部分 团队燃尽图 工作情况汇报 张扬(组长) 过去两天完成了哪些任务 文字/口头描述 准备四六级 展示GitHub当日代码/ ...
- 读入一个字符串str,输出字符串str中连续最长的数字串
要求: 读入一个长度不超过256的字符串,例如“abc123defg123456789hjfs123456”.要求输出“123456789” 思路: 遍历字符串,如果是数字串则计算往后一共有多少个数字 ...
- Selenium HTMLTestRunner 执行测试成功但无法生成报告
为什么用PyCharm或者Eclipse执行测试成功但无法生成HTMLTestRunner报告 最近遇到一些人问这样的问题: 他们的代码写的没问题,执行也成功了,但就是无法生成HTMLTestRunn ...
- linux中ls -l介绍
[root@localhost ~]# ls -l 总计 152 -rw-r--r-- 1 root root 2915 08-03 06:16 a -rw------- 1 root root 10 ...