C提供了多种不同的模型或存储类别(storage class) 在内存中储存数
据。
程序存储在内存中,从硬件的角度看,每个存储的值都占用物理内存,c语言用对象(object)来描述一块内存。
从软件角度,程序需要一个方法来获取对象,如通过变量声明:
int entity = 3;
这里创建了一个标志符(identifier)。标志符是一个名字,在这种情况,标志符能指定(designate)特定对象的内容。在该例中, 标识符entity即是软件(即C程序) 指定硬件内存中的对象的方式。该声明还提供了储存在对象中的值。
变量名不是指定对象的唯一途径。 见如下声明:
int * pt = &entity;
int ranks[10];
首先,pt 是一个标志符,它指定了一个用来存放地址的对象。
其次,*pt不是标志符,因为它不是一个名字,但它也指定了一个对象,即entity的值。
**左值:**指定一个对象的表达式。
因此,entity 是一个标字符,也是一个左值。
*pt 是一个表达式也是左值。
可以用存储期(storage duration)来描述对象,存储期即为对象在内存中保留的时间。
标志符用来访问对象,可以用作用域(scope) 和链接(linkage) 描述标识符,这两者组合一起表明哪些部分的程序能使用该标志符。
不同的存储类别即为作用域,链接和存储期的不同组合。
作用域用来描述出程序能获取标志符的区域。
块 (block)是用一对花括号括起来的代码区域。
一个定义在快内的变量有一个快作用域,即它的可见范围为从定义的位置直到块结束的末尾。
另外, 虽然函数的形式参数声明在函数的左花括号之前, 但是它们也具有块作用域, 属于函数体这个块。
并且,对于循环,如:for,while,do while等,虽然没有花括号,也
属于块,如:
for (int i = 0; i < 10; i++)
printf("A C99 feature: i = %d", i);
变量 i 只属于for循环,一旦循环结束,将不会有i变量。
函数作用域仅使用于 goto 语句的标签。如果一个标签第一次出现在一个函数内部,那么它的作用域是整个函数范围。
用法参考:C语言 goto 的作用域,用法
函数原型作用域针对函数声明中的变量,如:
int mighty(int mouse, double large);
上述 mouse 和 large两个变量的作用范围是从变量被定义到函数声明结束,因此函数原型声明时变量的名字不重要,可以不定义,编译器只关心变量的类型。
一个变量如果定义在任何函数的外面,则具有文件作用域,其作用范围是从定义的位置起直到该文件结束。如:
#include
int units = 0; /* a variable with file scope */
void critic(void);
int main(void)
{
...
}
void critic(void)
{
...
}
这里变量 units 即有文件作用域,可以被 main函数和critic两个函数使用。
因为文件作用域的变量可以用于多个函数,因此也被称为全局变量(global variables)
一个源文件可能包含多个头文件,而一个头文件也可能包含其他头文件,而在编译器编译时,c预处理器会直接将 #include 指令用头文件内容代替。
因此,编译器将整个包含源代码的文件以及相应的头文件看成一个翻译单元。
更多关于翻译单元的介绍见博客:
translation unit(翻译单元)
非典型性C语言教程-1.0 翻译单元,标识符,内部连接,外部连接
一个c变量的作用域是快作用域,函数作用域或函数原型作用域没有链接。这意味着该变量禁队块,函数或函数原型私有。
一个作用域是文件作用域可能有内部链接(internal linkage)或外部链接(external linkager)。
一个具有外部链接的变量可以对一个具有多个文件的程序使用。
一个具有内部链接的变量只能在一个翻译单元内使用。
注意:
c 标准用具有内部链接的文件作用域来描述文件作用域且仅限于一个翻译单元。
c 标准用具有外部链接的文件作用域来描述可以用于其他翻译单元的作用域。
为了简化描述,用文件作用域来代替内部链接的文件作用域,用全局作用域或程序作用域来描述外部链接的文件作用域
如何判断一个文件作用域具有内部链接还是外部链接:
可以通过查看外部定义中是否使用了存储类别说明符static:
int giants = 5; // file scope, external linkage
static int dodgers = 3; // file scope, internal linkage
int main()
{
...
}
...
作用域和链接描述了标志符的可视范围,而存储期描述了对象的生存期。
一个c对象有四个存储期:静态存储期(static storage duration)、线程存储期(thread storage duration)、 自动
存储期(automatic storage duration)、 动态分配存储期(allocated storage duration)。
一个具有静态存储期的对象存在于程序的整个执行时间内均。
所有的具有文件作用域的变量,无论是内部链接还是外部链接,均有静态存储期。
**注意:**一个文件作用域的变量前加上 static 关键字表明其为内部链接时,这里的 static 并非指存储期。
线程存储期用于并发程序设计, 程序执行可被分为多个线程。 具有线程
存储期的对象, 从被声明时到线程结束一直存在。 以关键_Thread_local声明一个对象时, 每个线程都获得该变量的私有备份。
一个具有快作用域的变量通常具有自动存储期。
对于这类变量,程序在进入该块区域定义变量的位置时会为其分配内存,当程序退出块时,释放分配的内存。这种做法相当于把自动变量占用的内存视为一个可重复使用的工作区或暂存区。
变长数组稍有不同, 它们的存储期从声明处到块的末尾, 而不是从块的
开始处到块的末尾。
一个属于自动存储类型的变量有自动存储期,块作用域,没有链接。
默认情况下, 声明在块或函数头中的任何变量都属于自动存储类别。
为了特意强调自动存储类别,可以用关键词 auto。
自动存储期意味着程序在进入该变量声明所在的块时变量存
在, 程序在退出该块时变量消失。
int loop(int n)
{
int m; // m in scope
scanf("%d", &m);
{
int i; // both m and i in scope
for (i = m; i < n; i++)
puts("i is local to a sub-block\n");
}
return m; // m in scope, i gone
}
此外,对于循环和if语句,即使没有花括号也是一个块,如:
int main()
{
int n = 8;
printf(" Initially, n = %d at %p\n", n, &n);
for (int n = 1; n < 3; n++)
printf(" loop 1: n = %d at %p\n", n, &n);
printf("After loop 1, n = %d at %p\n", n, &n);
for (int n = 1; n < 3; n++)
{
printf(" loop 2 index n = %d at %p\n", n, &n);
int n = 6;
printf(" loop 2: n = %d at %p\n", n, &n);
n++;
}
printf("After loop 2, n = %d at %p\n", n, &n);
return 0;
}
Initially, n = 8 at 0x7fff5fbff8c8
loop 1: n = 1 at 0x7fff5fbff8c4
loop 1: n = 2 at 0x7fff5fbff8c4
After loop 1, n = 8 at 0x7fff5fbff8c8
loop 2 index n = 1 at 0x7fff5fbff8c0
loop 2: n = 6 at 0x7fff5fbff8bc
loop 2 index n = 2 at 0x7fff5fbff8c0
loop 2: n = 6 at 0x7fff5fbff8bc
After loop 2, n = 8 at 0x7fff5fbff8c8
变量通常被存放在计算机内存中,寄存器变量被存放在 CPU 寄存器中,即存放在最快速可用的内存中,与常规的变量相比能被更快的访问和操作。
由于寄存器存放在寄存器而非内存中,无法获取寄存器变量的地址。
其他方面,寄存器变量与自动变量很类似,有块作用域,自动存储期,无链接。
寄存器变量声明时前面加上关键字 register。
**注意:**声明一个寄存器变量更像一个请求,因为编译器会根据可用的寄存器的数量来权衡是否答应请求,因此可能声明的变量只是一个普通的自动变量,然而依旧不能对它使用地址操作符。
静态变量中的静态值该变量在内存中保持不动,并非指变量的值不变。
具有文件作用域的变量自动的有静态的存储期。
也可以创建具有静态存储期、 块作用域的局部变量。 这种变量和自动变量有相同的作用域,但是程序离开它们所在的函数后, 这些变量不会消失。计算机在多次调用函数期间会记住他们的值。
这种变量在块内声明,前面加上关键词 static。
Listing 12.3 The loc_stat.c Program
/* loc_stat.c -- using a local static variable */
#include
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++);
}
Here comes iteration 1:
fade = 1 and stay = 1
Here comes iteration 2:
fade = 1 and stay = 2
Here comes iteration 3:
fade = 1 and stay = 3
程序分析:
在函数 trystat() 中,第一条语句 int fade = 1; 是该函数的一部分,因此函数每次被调用,该语句都会被执行一次。
而第二调语句 static int stay = 1; 其实并不属于该函数,当用调试器一步一步调试程序时会发现,该语句并未被执行,因为静态变量和外部变量都是在程序被倒入内存中时就已执行完。将该语句放入函数中只是告诉编译器这个变量只对该函数可见。
因此,不能将函数的形参声明为静态变量。
一个具有外部链接的静态变量具有文件作用域,外部链接,以及静态存储期。这种类型被成为外部存储类型,这种变量被称为外部变量。
int Errupt; /* externally defined variable */
double Up[100]; /* externally defined array */
extern char Coal; /* mandatory declaration if */
/* Coal defined in another file */
void next(void);
int main(void)
{
extern int Errupt; /* optional declaration */
extern double Up[]; /* optional declaration */
...
}
void next(void)
{
...
}
分析:
在 main() 函数中使用外部变量 Errupt 时可以不用再声明,因为外部变量有文件作用域,因此他们从声明的地方直到文件结束都是有效的,然而如果如果要在 main() 函数中再声明,必须加上关键字 extern,否则声明的就是一个普通的自动变量,与外部变量不是同一个。
/* Example 3 */
int Hocus; //外部变量
int magic();
int main(void)
{
int Hocus; // Hocus declared, is auto by default,只对main函数可见
...
}
int Pocus;
int magic()
{
auto int Hocus; // local Hocus declared automatic,只对 magic 函数可见
...
}
外部变量如果不初始化,则会自动初始化为0,这一规则也适用于外部定义的数组。
文件作用域的变量只能用常量表达式初始化。
int tern = 1; /* tern defined */
main()
{
external int tern; /* use a tern defined elsewhere */
}
这里,外部变量 tern 被声明了两次,第一次声明存储器预留了存储空间,接着定义其值大小,这种叫定义式声明(defining declaration)。
第二次有进行收入唵嘛仅仅告诉表一起来使用该变量,因此不是定义,这种叫引用式声明(referencing declaration)。
加上关键字 extern 表明是声明而非定义,因为它指示编译器去别处查询其定义。如:
extern int tern;
int main(void)
{
...
}
编译器会认为变量 tern 的实际定义在程序的其他地方或者其他文件中,这个声明并不会分配空间。
**因此,不要在给外部变量定义时加上 extern 关键字。只用它来引用别处的外部定义。**如:
// file one.c
char permis = 'N';
...
// file two.c
extern char permis = 'Y'; /* error */
第二次定义会出错,因为变量已经在另一个文件夹创建并初始化了。
内部链接的静态变量有静态的存储期,文件作用域,内部链接。
可以通过加上关键字 static 在所有函数的外部创建并定义这种变量。
static int svil = 1; // static variable, internal linkage
int main(void)
{
...
}
C语言有6个关键字作为存储类别说明符: auto、 register、 static、 extern、_Thread_local 和 typedef。 typedef关键字与任何内存存储无关, 把它归于此类有一些语法上的原因。 尤其是, 在绝大多数情况下, 不能在声明中使用多个存储类别说明符, 所以这意味着不能使用多个存储类别说明符作为typedef的一部分。 唯一例外的是_Thread_local, 它可以和static或extern一起使用。
auto 关键字表明一个变量有自动存储期,只能用于块作用域的变量声明中。由于在块中声明的变量本身就具有自动存储期, 所以使用auto主要是为了明确表达要使用与外部变量同名的局部变量的意图。
register 说明符也只能用于快作用域的变量。它把变量归为寄存器存储类
别, 请求最快速度访问该变量。 同时, 还保护了该变量的地址不被获取。
static 说明符创建一个有静态存储期的对象,它在程序被载入时创建,在程序结束时消失。
如果 static 关键字用于一个文件作用域的变量声明,则作用域限制在这个文件内。有内部链接。
如果 static 关键字用于一个块作用域的变量声明,则作用域限制在块内。无链接。
extern 说明符表明声明一个定义在其他地方的变量。
如果 extern 关键字用于一个文件作用域的变量声明,则变量有外部链接。
如果 extern 关键字用于一个块作用域的变量声明,则变量有外部链接或者内部链接,取决于变量的定义性声明(defining declaration )。
参考:extern关键字,C语言extern关键字用法详解
外部变量和静态变量会自动初始化为0,而自动变量和寄存器变量不会,其初始值不定。
外部变量和静态变量初始化必须是常量表达式,只会初始化一次,且在程序开始之行前就初始化。
自动变量和寄存器变量初始化不一定是常量表达式,且每次执行包含它们的函数快时就会初始化一次。