关键字有一定的语义,但是用法不唯一。

对于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有关的原理的更多相关文章

  1. C# 关键字extern用法

    修饰符用于声明在外部实现的方法.extern 修饰符的常见用法是在使用 Interop 服务调入非 托管代码时与 DllImport 属性一起使用:在这种情况下,该方法还必须声明为 static,如下 ...

  2. 【转载】理解C语言中的关键字extern

    原文:理解C语言中的关键字extern 最近写了一段C程序,编译时出现变量重复定义的错误,自己查看没发现错误.使用Google发现,自己对extern理解不透彻,我搜到了这篇文章,写得不错.我拙劣的翻 ...

  3. volatile关键字的作用、原理

    在只有双重检查锁,没有volatile的懒加载单例模式中,由于指令重排序的问题,我确实不会拿到两个不同的单例了,但我会拿到"半个"单例. 而发挥神奇作用的volatile,可以当之 ...

  4. 【校招面试 之 C/C++】第29题 C/C++ 关键字extern

    1.extern "C" extern "C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码.加上extern "C"后,会指示 ...

  5. 面试题:volatile关键字的作用、原理

    在只有双重检查锁,没有volatile的懒加载单例模式中,由于指令重排序的问题,我确实不会拿到两个不同的单例了,但我会拿到“半个”单例. 而发挥神奇作用的volatile,可以当之无愧的被称为Java ...

  6. C/C++关键字 extern

    1.基本解释:extern 可置于变量或函数前面,表示变量或函数的定义在别的文件中,以提示编译器遇到此变量或函数时在其他模块中寻找定义. extern还有另外2个作用.第一:与“C”连用时,如 ext ...

  7. 关键字 extern

    定义:extern可置于变量或者函数前,以表示变量或者函数的定义在别的文件中.编译器会到其他模块中寻找其定义. extern int f(); extern int i; extern关键字   作为 ...

  8. 18.21 关键字extern

    用#include可以包含其他头文件中变量.函数的声明,为什么还要extern关键字? 1.头文件 其实头文件对计算机而言没什么作用,只是在预编译时在#include的地方展开一下,没别的意义了.将头 ...

  9. Java的 volatile关键字的底层实现原理

    我们知道volatile关键字的作用是保证变量在多线程之间的可见性,它是java.util.concurrent包的核心,没有volatile就没有这么多的并发类给我们使用.本文详细解读一下volat ...

随机推荐

  1. OC的反射机制

    反射机制主要是指程序可以访问.检测和修改它本身状态或行为的一种能力.反射机制是指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法.对于人一个对象,都能够调用这个对象的任意方法和属性.这种 ...

  2. 【Python】【内置函数】

    [fromkeys()] -- coding: utf-8 -- python 27 xiaodeng python之函数用法fromkeys() fromkeys() 说明:用于创建一个新字典,以序 ...

  3. CSS自定义样式

    CSS自定义样式 1. 自定义字体 先将字体文件放到web服务器上,需要时自动下载到用户计算机上 属性:@font-face 例: @font-face{ font-family:myFont; sr ...

  4. (16)线程---定时器Timer

    # ### 定时器:指定时间执行任务 from threading import Timer def func(): print("目前正在执行任务") t = Timer(5,f ...

  5. django使用小结

    一.静态文件的使用 二.csrf跨站访问安全机制设置 三.MODEL模型使用

  6. form标签的 enctype属性

    1.enctype的定义: enctype 属性规定在发送到服务器之前应该如何对表单数据进行编码. 默认地,表单数据会编码为 "application/x-www-form-urlencod ...

  7. 『TensorFlow』0.x_&_1.x版本框架改动汇总

    基本数值运算 除法和模运算符(/,//,%)现在匹配 Python(flooring)语义.这也适用于 [tf.div] 和 [tf.mod].要获取基于强制整数截断的行为,可以使用 [tf.trun ...

  8. Matlab:线性热传导(抛物线方程)问题

    函数文件1:real_fun.m function f=real_fun(x0,t0) f=(x0-x0^2)*exp(-t0); 函数文件2:fun.m function f=fun(x0,t0) ...

  9. 2015-10-20 sql2

                                                                      SQL SERVER(二) 三.插入,更新,删除,添加 3.1ins ...

  10. Linux .vimrc 设置项

    Linux 下,.vimrc 有两个.一个是全局使用的(/etc/vimrc),另一个是个人使用的(~/.vimrc). 大部分的情况下,我们只需要设置自己目录下的.vimrc 即可. # vim ~ ...