0x00

C 语言的 external 变量总是神奇,像魔法一般的存在。要比较清晰地理解它,可以借助 node 的 require。

0x01

例如,如果们在 node 中有如下的代码:

  1. let foo = require('./constants.js').foo;

我们可以非常清晰地知道,我们从 ./constant.js 中获得了一个被 export 的、名叫 foo 的变量。来源一清二楚。

如果把上述代码转化成 C 的语法,则是简单的一句: external foo

初看这句话,我们可能会蹦出一大片的黑人问号,这是神马鬼?!哪里来的 foo ?从哪个文件引用过来的呢?从天上掉下来的吗?

作为上帝视角的 C 语言可以大胆地回答一句,yes。这其实和 C 的编译机制有关系,这里声明了有一个变量叫做 foo,但它的定义并不在本文件中。那在哪里呢?!编译器在编译阶段根本不知道。这必须要等到在 linking 阶段,只要能在别的 machine code 中(即 .o 文件中)找到名为 foo 的变量即可了。

也即是,从语法和编译机制的角度讲,可以在当前的任何一个 .c 文件中,都不存在一个叫做 foo 的变量定义。可以一直到 linking 阶段,在别的 machine code 中(即 .o 文件中)找到。

这就是为什么 C 语言中的 external 变量如此让人迷惑的原因。因为编译机制的灵活性,它让人无法知道所声明的变量来自何处。这就不如 node 中的 require 让人一目了然。

0x02

另一方面,如同 Bible C 中所阐述的,这个 external 修饰词,是相对于函数的。即函数的外部。

C 代码就两部分,一个是变量、一个是函数。凡是在函数外面声明的变量,都是 external 的。同样的,由于函数不能在另一个函数中被定义,所以每一个函数都是 external 的。

(这里需要澄清一下 global 和 external 的关系。C 语言是没有 global 这个关键词的,每一个声明在函数外部的变量和函数,就是全局的。而 external 修饰词,只是告诉当前源文件,这个变量是来自于外部的、另一个文件的全局变量。)

也即是,对于「函数」和「定义在函数外的变量」来讲,它们天然是对其它的 source code 是可见的

0x03

那么,有没有办法让一个全局的变量、函数,仅仅对本源文件可见,而对其它源文件不可见呢?

那就必须加上 static 修饰词。如此,声明在函数外部的变量和函数,只要加了 static,那么它对于当前源文件来讲,是全局的,但对于外部的变量来讲,是不可见的。

static 修饰词,我的理解是,它表明:被修饰的对象是当前”宿主”(寄宿在函数中,或者寄宿在文件中)的 meta info,即:仅对当前这个”宿主”可见、且相对于这个”宿主”是独一份的。

显然,这个解释对于上述的“static 修饰的全局变量”对外部源文件不可见是成立的:它寄宿在源文件文件中,相对于这个源文件是可见的,且是独一份的。

进一步,对于函数内部的有 static 修饰的变量,其作用也是成立的:它意味着,这个变量仅对所”宿主”的这个函数是可见的,并且,它相对于这个寄宿的函数来讲是独一份的。于是,它可以跨循环、跨递归地保留自己的变量值。