[原创] 从作用域 链接属性 存储类型三个方面详解 static & extern关键字
首先, 作用域范围有4类, 分别是文件作用域, 代码块作用域, 原型作用域和函数作用域. 本文只涉及文件作用域和代码块作用域. 原型作用域只适用于函数原型(非函数定义)中声名的参数名. 函数作用域不推荐, 故在此亦不解释.
位于一对花括号之间的所以语句称为一个代码块. 所以代码块作用域就是一对花括号之间(代码块)有效. 代码块作用域可以嵌套, 当嵌套的代码块存在重名标识符时, 内层标识符将隐藏外层标识符(即在内层代码块无法访问外层代码块的这个同名标识符),
而正常情况下(即一个代码块不存在同名标识符)的嵌套, 内层代码块是可以访问外层标识符的. 因此, 我们应该避免在嵌套的代码块中出现同名标识符.
所谓的文件作用域, 是指在任何代码块之外声明的标识符, 这些标识符的有效范围从声明位置到源文件结尾. 可以这么理解, 将排除掉函数作用域和原型作用域的作用域比作一个全集, 那在同一源文件中(暂不涉及extern、include), 文件作用域和代码块作用域就是互为补集. 在代码块中声名的标识符就是代码块作用域, 而不在任何代码块中声名的标识符就是文件作用域.
对于作用域, 如存在同名标识符, 则遵循最小优先原则, 即范围小的作用域标识符优先访问, 在代码块中访问时, 先访问同名的代码块标识符.
链接属性决定如何处理不同文件的标识符, 有3种: external, internal, none. 没有链接属性的标识符的多个声明会被当做独立不同的实体. Internal连接属性的标识符在同一源文件中的所有声明被当做同一实体, 但在不同源文件中却作为不同实体. external属性的标识符无论在多少个源文件中被声明多少次, 都是表示同一个实体.
具有文件作用域的非函数标识符和具有任意作用域的函数标识符缺省链接属性是external, 其余均为none.
static关键字可以将缺省链接属性为external的的声明改为internal. 但并不改变标识符的作用域和存储类型.
extern关键字用来将一个标识符指定为external链接属性, 尽管具有文件作用域的的标识符缺省链接属性就是external, 但还是建议使用 extern关键字显式指定. 另外要注意的是, extern只能用于标识符第一次声明, 如果用于后几次声明, 它不会改变由第一次声明指定的链接属性.
变量的存储类型也有三种, 分别是静态变量, 自动变量和寄存器变量, 而与之对应的三个存储位置分别是普通内存, 堆栈(动态内存) 和寄存器.
变量的缺省存储类型取决于声明位置. 具有文件作用域的变量存储于普通内存, 在程序运行前创建, 无法被指定为其他存储类型, 故称为静态变量; 具有代码块作用域的变量缺省存储类型是自动的,
存储于堆栈, 只在程序执行到相应代码块才被创建, 这就是自动变量. 自动变量可以通过添加关键字变为静态变量. 但该变量的作用域仍然是代码块作用域.
总结, 具有external链接属性的实体总是具有静态存储类型.具有文件作用域的标识符默认链接属性为external, 可以使用static将其链接属性变为internal. 具有代码块作用域的变量默认自动存储类型, 链接属性为none, 可以使用static将其存储类型改为静态变量.
在任何代码块之外(文件作用域)声明的变量被称为静态变量, 在代码块内部声明的变量(代码块作用域)缺省存储类型是自动的. 自动变量可以通过关键字static变为静态变量, 但作用域仍为代码块.
==============================================================================================================================================================================================================================
修订补充从这里开始, 主要是针对使用场景而言.
首先对于任何代码块之外的变量, 它就是静态变量, 不需要关键字static来说明存储类型, 我目前的理解就是静态全局变量不能显式说明. 而出现在全局静态变量之前的关键字用来改变标识符的链接属性, 从external变为internal. 此时该标识符
只能在其声明的源文件中被访问. static只有修饰具有代码块作用域的自动变量才会将标识符变为静态变量. 但局部静态变量的作用域仍为代码块作用域, 链接属性为none.
extern只用于为标识符指定external链接属性.
参考:
<<C和指针>>