本文是作用域和存储类型的总结,以一个例子来说明,如果不看解释可以很直接地回答每一条语句的作用域和存储类型,那么说明已经很熟练地掌握这个知识点了。
关于作用域和存储类型可以参考我前面的博客:
《C和指针》笔记10:作用域
《C和指针》笔记12: 存储类型(自动变量、静态变量和寄存器变量)
题目
请看下面的代码,回答每一行的标识符的链接属性、存储类型和作用域
答案
行数 | 标识符 | 链接属性 | 存储类型 | 作用域 |
---|---|---|---|---|
1 | a | external | 静态存储 | 第1-第12行,第17-第29行 |
2 | b | external | 静态存储 | 第2-第6行,第25-第29行 |
3 | c | internal | 静态存储 | 第3-第29行 |
4 | d | external | 静态存储 | 第4行-文件结束 |
4 | e | external | 自动存储 | 第6-11行,第17-19行以及第23-24行 |
6 | f | 不具有链接属性 | 自动存储 | 至函数结束 |
7 | b | 不具有链接属性 | 寄存器存储 | 至函数结束 |
8 | g | 不具有链接属性 | 静态存储 | 至函数结束 |
9 | a | external | 静态存储 | 和第一行的a 一样 |
12 | e | 不具有链接属性 | 自动存储 | 延伸至16行 |
13 | g | 不具有链接属性 | 自动存储 | 延伸至16行 |
14 | h | external | 静态存储 | 当前的代码块 |
19 | x | 不具有链接属性 | 自动存储 | 当前的代码块 |
20 | e | 不具有链接属性 | 自动存储 | 当前的代码块 |
25 | i | internal | 静态存储 | 从它声明的位置直到这个源文件结束 |
解释
- 第1行的a
- 链接属性:
external
,因为它属于文件作用域的声明在缺省情况下为external
链接属性,可以被其他源文件访问。 - 存储类型:静态存储。
- 作用域:第1-第12行,第17-第29行。静态变量在程序执行之前创建,并一直保持它们的值,直到程序结束。当程序开始执行时,变量a将初始化为5。其作用域应一直延伸到这个源文件结束为止。但是第13行出现的a的局部变量将隐藏同名的静态变量。
- 链接属性:
- 第2行的b
- 链接属性:
external
,如果b的定义在其他地方,第2行的extern
关键字在技术上并非必需,但在风格上却是加上这个关键字为好,可以被其他源文件访问。 - 存储类型:静态存储。
- 作用域:第2-第6行,第25-第29行。静态变量在程序执行之前创建,并一直保持它们的值,直到程序结束。其作用域应一直延伸到这个源文件结束为止。 但是由于第7行出现的b的局部变量将隐藏同名的静态变量。
- 链接属性:
- 第3行的c
- 链接属性:
internal
。static
关键字修改了c的缺省链接属性,把它改为internal
。只能被当前的源文件访问。 - 存储类型:静态存储。
- 作用域:第3-第29行。静态变量在程序执行之前创建,并一直保持它们的值,直到程序结束。其作用域应一直延伸到这个源文件结束为止。
- 链接属性:
- 第4行的d
- 链接属性:
external
。作为函数名,d在缺省情况下具有external链接属性,所以其他源文件只要在文件上存在d的原型 ,就可以调用d。函数d的定义对于这个源文件中任何以后想要调用它的函数而言起到了函数原型的作用,如果我们将函数声明为static,就可以把它的链接属性从external改为internal,但这样做将使其他源文件不能访问这个函数。 - 存储类型:静态存储。对于函数而言,存储类型并不是问题,因为代码总是存储于静态内存中。
- 作用域:第4行直到文件结束。静态变量在程序执行之前创建,并一直保持它们的值,直到程序结束。其作用域应一直延伸到这个源文件结束为止。
- 链接属性:
- 第4行的e
- 链接属性: 不具有链接属性,所以我们只能从函数内部通过名字访问它。
- 存储类型:自动存储。在函数被调用时被创建,当函数返回时消失。
- 作用域:第6-11行,第17-19行以及第23-24行。与局部变量冲突导致了作用域的变化。
- 第6行-第8行的f,b,g
- 链接属性: 不具有链接属性,所以它们不能在函数的外部通过名字访问(这是它们称为局部变量的原因)
- 存储类型:
- f是自动存储,当函数每次被调用时,它通过隐式赋值被初始化为15。
- b是寄存器存储,所以它的初始值是垃圾。
- g是静态存储,在程序的整个执行过程中一直存在。当函数每次被调用时,它并不会被重新初始化。
- 作用域:到函数结束为止。由于与局部变量冲突,它的作用域限于。
- 第9行的a
- 它的声明并不需要。这个代码块位于第1行声明的作用域之内。所以它和第一行的a的连接属性、存储类型都是一样的。
- 第12行的-第13行的e,a
- 链接属性: 不具有链接属性,和先前声明的a和e不同,在这个代码块中,以前声明的同名变量是不能被访问的。
- 存储类型:自动存储
- 作用域:延伸至第16行。
- 第14行的h
- 链接属性:
external
。相当一个全局变量。这是唯一一个必须使用extern关键字的声明,如果没有它,h将变成另一个局部变量。 - 存储类型:静态存储
- 作用域:当前的代码块。
- 链接属性:
- 第19和20行的x,e
- 链接属性: 不具有链接属性,和先前声明的a和e不同,在这个代码块中,以前声明的同名变量是不能被访问的。
- 存储类型:自动存储
- 作用域:当前的代码块。
这里的e和第4行函数的形参e是不同的变量,它和第12行声明的e也不相同。在这个代码块中,从第11行到第18行并无嵌套,所以编译器可以使用相同的内存来存储两个代码块中不同的变量e。如果想让这两个代码块中的e表示同一个变量,那么你就不应该把它声明为局部变量。
- 第25行的i
- 链接属性:
internal
,该属性可以防止它被这个源文件之外的任何函数调用。其他的源文件也可能声明它自己的函数i,但它与这个源文件的i是不同的函数。 - 存储类型:静态存储
- 作用域:从它声明的位置直到这个源文件结束。
- 链接属性:
函数d不可以调用函数i,因为在d之前不存在i的原型。
总结
具有external
链接属性的实体在其他语言的术语里称为全局(global)实体,所有源文件中的所有函数均可以访问它。只要变量并非声明于代码块或函数定义内部,它在缺省情况下的链接属性即为external
。如果一个变量声明于代码块内部,在它前面添加extern关键字将使它所引用的是全局变量而非局部变量。除了实体的具体定义位置之外,在它的其他声明位置都使用extern
关键字。
具有external
链接属性的实体总是具有静态存储类型。全局变量在程序开始执行前创建,并在程序整个执行过程中始终存在。从属于函数的局部变量在函数开始执行时创建,在函数执行完毕后销毁,但用于执行函数的机器指令在程序的生命期内一直存在。
局部变量由函数内部使用,不能被其他函数通过名字引用。它在缺省情况下的存储类型为自动,这是基于两个原因:
- 当这些变量需要时才为它们分配存储,这样可以减少内存的总需求量。
- 在堆栈上为它们分配存储可以有效地实现递归。
如果觉得让变量的值在函数的多次调用中始终保持原先的值非常重要的话,可以修改它的存储类型,把它从自动变量改为静态变量。
变量类型 | 声明的位置 | 是否存在于堆栈 | 作用域 | 如果声明为static |
---|---|---|---|---|
全局 | 所有代码块之外 | 否 | 从声明处到文件尾 | 不允许从其他源文件访问 |
局部 | 代码块起始处 | 是 | 整个代码块 | 变量不存储于堆栈中,它的值在程序整个执行器一直保持 |
形式参数 | 函数头部 | 是 | 整个函数 | 不允许 |
参考
- 《C与指针》