文章目录
- 作用域
- 块作用域(block scope)
- 函数作用域(function scope)
- 函数原型作用域(function prototype scope)
- 文件作用域(file scope)
- 翻译单元和文件(作用域)
- 链接(linkage)
- 存储期(Storege Duration)
- 静态存储期(static storage duration)
- 线程存储期(thread storage duration)
- 自动存储期(automatic storage duration)
- 动态分配存储期(allocated storage duration)
- 块作用域的静态变量(Static Variables with Block Scope)
- 外部链接的静态变量(Static Variables with External Linkage)
- 内部链接的静态变量(Static Variables with Internal Linkage)
- 多文件(multiple file)
背景:
作为一名新手,最近在用C代码实现某个项目,文件比较多,所以第一次接触到C代码对函数、结构体的封装,有点懵懵的,不知道为什么这么写,研究后发现,竟然是对作用域的理解还不是很清晰!
所以,特意写此博客,帮助理解,内容都来自于C Primer Plus。
并且我的博客删减内容较多,适合个人理解用,如果想看详细讲解,请移步C Primer Plus 第十二章
C语言多文件编程需要了解的基础知识
作用域
块作用域(block scope)
某种程度来说,花括号括起来的都是块。
块作用域变量的可见范围是 从定义处到包含该定义的块的末尾。另外,虽然函数的形式参数声明在函数 的左花括号之前,但是它们也具有块作用域,属于函数体这个块。
函数作用域(function scope)
函数作用域指的是在函数内部定义的局部变量的作用域。这些变量只在函数体内部可见,一旦函数执行完毕,这些变量的生命周期就结束了。函数参数也属于函数作用域。
函数原型作用域(function prototype scope)
函数原型作用域指的是在函数原型中声明的参数名的作用域。这些参数名只在函数原型声明中有效,主要用于文档化目的,以表明函数期望的参数类型。函数的实现可以使用与原型中不同的参数名
Function prototype scope runs from the point the variable is defined to the end of the prototype declaration. What this means is that all the compiler cares about when handling a function prototype argument is the types; the names you use, if any, normally don’t matter, and they needn’t match the names you use in the function definition.
#include <stdio.h>
// 函数原型,其中 a 和 b 的作用域仅限于这个原型
int add(int a, int b);
int main() {
printf("%d\n", add(2, 3));
return 0;
}
// 函数实现,可以使用与原型不同的参数名
int add(int x, int y) {
return x + y;
}
文件作用域(file scope)
file scope variables are also called global variables
#include <stdio.h>
int units = 0;
void critic(void);
int main(void) {
...
}
void critic(void) {
...
}
其中units
有文件作用域,更准确来说它具有外部链接文件作用域(units has file scope with external linkage,下文会对其进行描述)
翻译单元和文件(作用域)
什么是翻译单元(Translation Units)
你认为的多个文件在编译器中可能其实是以一个文件出现。
例如,通常在源代码(.c)中包含一个或多个头文件(.h )。头文件会依次包含其他头文件,所以会包含多个单独的物理文件。但是,C预处理实际上是用包含的头文件内容替换#include指令。所以,编译器源代码文件和所有的头文件都看成是一个包含信息的单独文件。这个文件被称为翻译单元 (translation unit)。
描述一个具有文件作用域的变量时,它的实际可见范围是整个翻译单元。如果程序由多个源代码文件组成,那么该程序也将由多个翻译单元组成。每个翻译单元均对应一个源代码文件和它所包含的文件。
这其实扩大了我们所谓文件作用域的范围。
链接(linkage)
A C variable has one of the following linkages: external linkage, internal linkage, or no linkage.
无链接变量:具有块作用域、函数作用域或函数原型作用域的变量,这意味着这些变量属于定义它们的块、函数或原型私有。
内部链接/外部链接变量:具有文件作 用域的变量可以是外部链接或内部链接。
Note Formal and Informal Terms
The C Standard uses “file scope with internal linkage” to describe scope limited to one translation unit (a source code file plus its included header files) and “file scope with external linkage” to describe scope that, at least potentially, extends to other translation units. But programmers don’t always have the time or patience to use those terms. Some common short cuts are to use “file scope” for “file scope with internal linkage” and “global scope” or “program scope” for “file scope with external linkage.”
往往把“内部链接的文件作用域”叫做文件作用域;
把“外部链接的文件作用域”叫做全局作用域或程序作用域
案例如下:
int giants = 5; //file scope, external linkage
static int dodgers = 3; //file scope, internal linkage
int main() { ...
} ...
该文件和同一程序的其他文件都可以使用变量giants
。而变量dodgers
属文件私有,该文件中的任意函数都可使用它。
存储期(Storege Duration)
Scope and linkage describe the visibility of identifiers. Storage duration describes the persistence of the objects accessed by these identifiers.
存储器描述了变量的生命周期
静态存储期(static storage duration)
如果对象具有静态存储期,那么它在程序的执行期间一直存在。文件作用域变量具有静态存储期。注意,对于文件作用域变量,关键字 static表明了其链接属性,而非存储期。以 static声明的文件作用域变量具有内部链接。但是无论是内部链接还是外部链接,所有的文件作用域变量都具有静态存储期。
线程存储期(thread storage duration)
用于并发程序设计,程序执行可被分为多个线程。具有线程存储期的对象,从被声明时到线程结束一直存在。以关键字_Thread_local声明一个对象时,每个线程都获得该变量的私有备份。
自动存储期(automatic storage duration)
块作用域的变量通常都具有自动存储期。当程序进入定义这些变量的块时,为这些变量分配内存;当退出这个块时,释放刚才为变量分配的内存。 这种做法相当于把自动变量占用的内存视为一个可重复使用的工作区或暂存区。例如,一个函数调用结束后,其变量占用的内存可用于储存下一个被调用函数的变量。
为什么自动呢?就是因为这些对内存的操作都是系统为我们完成的。
不过,
然而,块作用域变量也能具有静态存储期。为了创建这样的变量,要把变量声明在块中,且在声明前面加上关键字static
所以,你能理解自动变量吗?
动态分配存储期(allocated storage duration)
也就是我们熟悉的malloc
、calloc()
、realloc
、free()
块作用域的静态变量(Static Variables with Block Scope)
静态变量(static variable)听起来自相矛盾,像是一个不可变的变量。 实际上,静态的意思是该变量在内存中原地不动,并不是说它的值不变。具有文件作用域的变量自动具有(也必须是)静态存储期。前面提到过,可以创建具有静态存储期、块作用域的局部变量。这些变量和自动变量一样,具 有相同的作用域,但是程序离开它们所在的函数后,这些变量不会消失。**也就是说,这种变量具有块作用域、无链接,但是具有静态存储期。**计算机在 多次函数调用之间会记录它们的值。在块中(提供块作用域和无链接)以存储类别说明符static(提供静态存储期)声明这种变量。
#include <stdio.h> void trystat(void);
int main(void) {
int count;
for (count = 1; count <= 3; count++)
{
printf("Here comes iteration %d:\n", count); trystat();]
}
return 0;
}
void trystat(void) {
int fade = 1;
static int stay = 1;
printf("fade = %d and stay = %d\n", fade++, stay++);
}
其中变量stay
就具有全局作用域
外部链接的静态变量(Static Variables with External Linkage)
外部链接的静态变量具有文件作用域、外部链接和静态存储期。该类别 有时称为外部存储类别(external storage class),属于该类别的变量称为外 部变量(external variable)。把变量的定义性声明(defining declaration)放 在在所有函数的外面便创建了外部变量。当然,为了指出该函数使用了外部 变量,可以在函数中用关键字extern再次声明。如果一个源代码文件使用的 外部变量定义在另一个源代码文件中,则必须用extern在该文件中声明该变 量。
int Errupt; //外部定义的变量
double Up[100]; //外部定义的数组
extern char Coal; //如果Coal被定义在另一个文件,则必须这样声明
void next(void);
int main(void)
{
extern int Errupt; //此时external为可选的声明
extern double Up[]; //此时external为可选的声明
...
}
void next(void)
{
...
}
注意,在main()
中声明Up
数组时(这是可选的声明)不用指明数组大小,因为第1次声明已经提供了数组大小信息。main()
中的两条 extern
声明完全可以省略,因为外部变量具有文件作用域,所以Errupt
和Up
从声明处到文件结尾都可见。它们出现在那里,仅为了说明main()
函数要使用这两个变量。
内部链接的静态变量(Static Variables with Internal Linkage)
该存储类别的变量具有静态存储期、文件作用域和内部链接。在所有函数外部(这点与外部变量相同),用存储类别说明符static定义的变量具有这种存储类别:
static int svil = 1;
int main (void)
{
...
}
就是声明一个静态的全局变量
多文件(multiple file)
只有当程序由多个翻译单元组成时,才体现区别内部链接和外部链接的重要性。
复杂的C程序通常由多个单独的源代码文件组成。有时,这些文件可能要共享一个外部变量。**C通过在一个文件中进行定义式声明,然后在其他文件中进行引用式声明来实现共享。**也就是说,除了一个定义式声明外,其他声明到要使用extern关键字。而且,只有定义式声明才能初始化变量。
注意,如果外部变量定义在一个文件中,那么其他文件在使用该变量之 前必须先声明它(用 extern
关键字)。也就是说,在某文件中对外部变量进 行定义式声明只是单方面允许其他文件使用该变量,其他文件在用extern
声明之前不能直接使用它