最近在读《程序员的自我修养——连接、装载与库》,感受本身当初学习C的时候,对extern、static等关键字了解不是特别清晰,所以重温了一遍《C和指针》中关于做用域、连接属性和存储类型的相关部分,加上了本身的理解,用博客记录一下。程序员
当变量在程序的某个部分被声明时,它只有在程序的必定区域才能被访问。函数
这个区域由标识符的做用域决定,标识符的做用域就是程序中该标识符能够被使用的区域。学习
据我所学,编译原理中有讲到,检查变量的做用域是否合乎规则,是在编译中的语义分析时查看的。优化
编译器能够确认4种不一样类型的做用域——文件做用域、函数做用域、代码块做用域和原型做用域。标识符声明的位置决定了它的做用域。指针
代码块做用域调试
位于一堆花括号之间的全部语句称为一个代码块,任何在代码块的开始位置声明的标识符具备代码块做用域。代表他们能够被这个代码块中的全部语句访问。code
下图中的a、b、c、d和arg均具备代码块做用域。blog
/* main.c */ #include <stdio.h> int g; int func(int x); int main(int argc, char* argv[]) { int a; int b; a = 5; { int c; int a; //隐藏外部的a,外层的那个标识符将没法在内层代码块中经过名字访问。 c = 5; a = 10; printf("%d", c + a); //打印结果:15,而不是10 } { int d; func(d); } } int func(int arg) { // }
注:咱们应当避免在嵌套的代码块中出现相同的变量名,由于并无很好的理由使用这种技巧,他们只会在程序的调试或维护期间引发混淆。内存
文件做用域作用域
任何在全部代码块以外声明的标识符都具备文件做用域(file scope),他表示这些标识符从他们的声明之处直到他所在的源文件结尾处都是能够访问的。g、func和main都具备文件做用域。这也就是为何咱们要将func的声明单独写在main函数前,就是为了main能够调用func函数,不然main是不能够访问到func函数的。
原型做用域
原型做用域只适用于在函数原型中声明的参数名,如func声明语句中的x。
函数做用域
只适用于语句标签,语句标签用于goto语句。
后两种做用域很是很是不常见,所以咱们应当把关注点放在前两个做用域上面。
标识符的连接属性(Linkage)决定如何处理在不一样文件中出现的标识符。标识符的做用域与它的连接属性有关。但这两个属性并不相同。
/* main.c */ #include <stdio.h> int g; int func(int x); int main(int argc, char* argv[]) { int a; int b; a = 5; { int c; int a; //隐藏外部的a,外层的那个标识符将没法在内层代码块中经过名字访问。 c = 5; a = 10; printf("%d", c + a); //打印结果:15,而不是10 } { int d; func(d); } } int func(int arg) { // }
external
属于external连接属性的标识符无论声明多少次,位于几个源文件都表示同一个实体。
缺省状况下,声明在任何代码块以外的变量或函数(即具备文件做用域)具备external连接属性,其他都为none。代码中g、func和main连接属性都是external,其他的变量连接属性均为none。
extern关键字:
internal
具备internal连接属性的标识符在同一个源文件内的全部声明都指同一个个体,但位于不一样源文件的多个声明则分属不一样的实体。
若是某个声明在正常状况下具备external连接属性,在他面前加上static关键字,可使他的连接属性变为internal。例如若是g的声明为static int g;
,那么变量g就变为源文件私有。其余源文件若是要连接g的变量,引用的是另外一个不一样的变量,相似的,函数声明也能够是static,如static int func(int x);
。
static只有对缺省连接属性为external的声明才有改变连接属性的效果。
none
没有连接属性的标识符(none)老是被看成单独的个体,也就是说该标识符的多个声明被看成独立不一样的个体。
变量的存储类型(storage class)是指存储变量值的内存类型。变量的存储类型决定变量什么时候建立、什么时候销毁以及它的值将保持多久。有三个地方能够用于存储变量:
普通内存
凡是在任何代码块以外声明的变量(具备文件做用域、external连接属性)老是存储于静态内存,这类变量称为静态变量,放在二进制文件的.data段或bss段中。
静态变量在程序运行以前建立,在程序的整个执行期间始终存在。他始终保持原先的值,除非给他附一个不一样的值或程序结束。
运行时堆栈
在代码内部声明的变量的缺省存储类型是自动的。也就是说他存储于堆栈中,称为自动变量。
若是给他加上关键字static,可使他的存储类型从自动变为静态(放在.data段或.bss段中)。具备静态存储类型的变量在整个程序执行过程当中一直存在,而不只仅在声明它的代码块的执行时存在。注意,修改变量的存储类型并不表示修改该变量的做用域,他虽然始终存在,可是仍是只能在该代码块内声明事后,按名字访问。
硬件寄存器
用于自动变量的声明,提醒他们应该存储于机器的硬件寄存器,而不是内存中,这类变量称为寄存器变量。可是编译器并不必定要理睬register关键字,也就是说不是你在变量前加了register关键字,这个变量最后就被存储于机器的硬件寄存器里面了,仍是要看编译器的”心情“的,即取决于编译器的优化方案😄。
注:register变量是不提供地址的哦。
初始化静态变量不须要额外的时间和开销,变量将会获得正确的值,若是不显示地指定其初始值,静态变量将初始化为0。由于静态变量直接存在.data段或.bss段里面,在生成目标文件时已经被编译器写进去了,因此运行时确定不花时间。
自动变量的初始化须要更多开销由于当程序连接时还没法判断自动变量的存储位置。事实上,函数的局部变量在函数的每次调用中可能占据不一样的位置,所以基于这个理由,自动变量没有缺省的初始值,而显式的初始化将在代码块的起始处插入一条隐式的赋值语句。这里的隐式我认为就是代码段中插入了一条赋值语句如mov [ebp -4] , value
,这样的话就形成初始化和先声明后赋值效率并没有提升,只有风格之差。
当用于不一样的上下文环境时,static关键字具备不一样的意思。
extern关键字