理解C语言的external变量和static关键词

理解C语言的external变量和static关键词

C语言的external变量总是神奇,像魔法一般的存在。要比较清晰地理解它,可以借助node的require。例如,如果们在node中有如下的代码:

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让人一目了然。

另一方面,如同Bible C中所阐述的,这个external修饰词,是相对于函数的。即函数的外部。C代码就两部分,一个是变量、一个是函数。凡是在函数外面声明的变量,都是external的。同样的,由于函数不能在另一个函数中被定义,所以每一个函数都是external的。

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

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

那么,有没有办法让一个全局的变量、函数,仅仅对本源文件可见,而对其它源文件不可见呢?那就必须加上static修饰词。如此,声明在函数外部的变量和函数,只要加了static,那么它对于当前源文件来讲,是全局的,但对于外部的变量来讲,是不可见的。

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

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

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

发表评论