1、一些必要的基础概念
(1)对象
- 从硬件的角度,被存储的每个值都被占用了一定的物理内存,C语言把这样的一块内存称为对象
- 对象可以存储一个或多个值
- 一个对象可能并未存储实际的值,也可能存储一个或多个值,但是在存储是适当的值时一定具有相应的大小
- 要注意的是,面向对象编程语言(例如C++)的“对象”指的是“类对象”不是本章讨论的“对象”,其定义包括数据和允许对数据的操作
(2)标识符
int entity = 3;
int* pt = &entity;
int ranks[10];
- 标识符是一个名称,用来指定特定对象的内容。例如上面的声明创建了一个标识符entity,entity即是C语言指向硬件内存中的对象的方式
- 但是变量名不是对象的唯一途径,比如pt整体不是一个标识符,但是可以通过pt来指向一个对象
(3)左值
- 指定对象的表达式被成为“左值”,提到左值意味着:
- ①它指定一个对象,可以引用内存中的地址
- ②它可以出现在赋值运算符的左侧
- entity是标识符也是左值;*pt是表达式也是左值;
- ranks+2*entity不是标识符,也不是左值;*(ranks+2*entity)是表达式,也是左值
- 可修改左值:后来新增了const修饰符,const修饰的变量不可修改,满足第一点但不满足第二点。一方面C语言继续把某些标识对象的表达式定义为左值,另外一方面某些左值不能放在赋值运算符的左侧,因此多了一个细分概念,即“可修改的左值”,当前标准建议用术语“对象定位值”更好
(4)翻译单元和文件
- 我们认为的多个文件可能会在编译器中以一个文件的形式出现。比如通常在.c文件中,会使用大量的.h文件,头文件还有可能包含其他的.h文件,C预处理器实际上是用包含的头文件内容替换掉#include指令。所以编译器把源文件代码和多个头文件看成是一个包含信息的单独文件,这个文件就叫做“翻译单元”
- 如果程序由多个源代码构成,那么程序也应该由多个翻译单元,每个翻译单元对应一个源代码文件和它所包含的头文件
2、对“对象”和“标识符”的描述
(1)可以用“存储期”描述对象
存储期:描述通过这些标识符访问对象的生存期
① 静态存储期,一个变量如果具有静态存储期,那么它在程序的执行期间会一直存在。文件作用域变量就具有静态存储期(无论其是内部还是外部属性)注意注意,对于文件作用域变量,关键字static不是指改变成静态(因为本来就具有静态变量),而是改变其链接
② 线程存储期,线程存储期多用于并发程序开发,程序执行被分为多个线程。具有线程存储期的对象,从声明到线程结束的时候一直都存在。以关键字_Thread_local声明一个对象时,每个线程都可以获得该变量的私有备份
③ 自动存储期,块作用域的变量通常具有自动存储期。当程序进入定义这些变量的块时,为这些变量分配内存,当退出这个块时,释放刚才为变量分配的内存。这种做法相当于把自动变量占用的内存视为一个可以重复使用的工作区或暂存区。不过变长数组略有不同,它们的存储器从声明处到块的末尾。所有使用的局部变量默认都是自动类别。要想要局部变量具有静态存储期,可以在声明前加上关键字static使其具有静态存储期
void more(int number)
{
int index;
static int ct = 0;
//某些代码
return 0;
}
变量ct存储在静态内存中,它从程序中被载入到程序结束期间都存在,但是,他的作用域在块内,只有在使函数more时,才可以直接使用ct访问它指向的对象(但是可以让more函数提供该存储区的地址以便于间接访问该对象)
④ 动态分配存储期,这个涉及到动态分配内存,比较复杂,暂时不讨论
(2)可以用“作用域”和“链接”描述标识符
作用域:描述程序中可以访问标识符的区域
① 块作用域,主要体现在{}内和函数的形参
void function(void)
{
int j = 100;
{
int j = 10;
printf("%d", j);
for(int i = 0; i < 10; i++)
{
printf("%d", i);
}
}
printf("%d", j);
}
变量i只有在最内层{}才可以被使用,j在第二层{}可以被使用(注意j此时值为10而不是100),最外层j的值为100。另外for循环里的i虽然不在最内层{}里,但是也是属于最内存的,具有块作用域,这种特性包括if、while、do等
② 函数作用域,主要体现在用于goto的标签,这意味着即使goto的标签首次出现在函数的内层块中,它的作用域也是延申到整个函数,这就避免了在一个函数中使用同名标签的情况,避免了混乱
③ 函数原型作用域,主要是用于函数原型中的形参名,范围是从形参定义处到原型声明结束。这就意味着编译器在处理函数原型中的形式参数时只关心它的类型,形参名字通常无关紧要。即使在函数声明中有形参名,也不必与函数定义中的形参相同。
只有在带有变长数组参数的函数中,形参名才有用void function(int n, int m, arr[n][m]);
④ 文件作用域,文件作用域变量通常也叫做全局变量,“文件”的意义就在于:具有文件作用域的变量,从它的定义处到该文件到定义所在文件的结尾都可以被使用
int units = 100;
void function(void);
int main()
{
//某些代码
return 0;
}
void function(void)
{
//某些代码
}
其中units就是全局变量,具有文件作用域,units从定义开始可以到文件结尾全都可见
链接:描述如何处理在不同文件中出现的标识符
①外部链接,具有文件作用域的变量可以是外部链接和内部链接,默认情况下全局变量具有外部链接属性,在其他文件需要这个变量时,要用extern提醒编译器这是一个外部变量
②内部链接,具有文件作用域的变量可以是外部链接和内部链接,可以通过static修饰变量来改变成内部链接,只给一个文件私有
③无链接,具有块作用域、函数作用域、函数原型作用域的变量都是无链接变量(这很符合“黑盒”这个说话)
3、c语言的存储类别
C提供了很多的存储类
4、参考来源
来自书籍《C primer puls》第12章的“存储类别、链接和内存管理”章节