持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第28天,点击查看活动详情
前言
本文就来分享一波作者对C中存储类别的学习心得与见解,主要介绍作用域和链接,还有后文。
笔者水平有限,难免存在纰漏,欢迎指正交流。
存储类别
前置铺垫
程序中的数据存储在内存中,从硬件方面来看,被存储的每个值都要占用一定的物理内存,而C语言就把这样的一块内存称为对象(区别于面向对象编程的对象)。对象可以存储一个或多个值。
好了,对象里面存入了值,如何使用呢?程序需要一种方法访问对象,可以通过定义变量来完成:
int val = 10;
该定义创建了一个名为val的标识符,由这个标识符来指定特定的对象的内容。也就是我们需要给定一个名称才能很好地找到并使用我们想要用的对象的值,要是没有命名的话谁知道哪个对象放的是想要的值。
变量名不是指定对象的唯一途径,还可以通过指针解引用来指定,比如
int *p = &val;
*p = 2;
还可以通过什么指定对象呢?其实还有字面量,何谓字面量?字面量简单来说就是各种类型数据的值,是除了符号常量以外的常量,比如123,'a',"hello world",6.66等等。实际上常量也存储在内存中,只是存的地方是一个只读区域。所以字面量也可以指定对象。
之前的博客有提到过左值,详情请移步至:[深入浅出C语言]左值右值以及其他操作符 - 掘金 (juejin.cn)
左值又被称为对象定位值,顾名思义,就是可以用来定位对象的值,也就是这个值可以指定特定的对象,而可以指定对象的有哪些东西呢?综合前面讲的,有以下几种:
标识符
某些表达式
字面量
定义一个数组
int rank[10];
那么rank + 2 * val
这个表达式是不是左值呢?
不是,它既不是标识符,又没有指定对象,也就不是左值,实际上这个表达式的值是临时的,如果没有赋值给某个变量,语句结束后就消失了。但是*(rank + 2 * val)
这个表达式就是左值,因为它制定了特定的对象,原本是指针偏移得到的地址,解引用后就指向对应地址处的数值,也就指向了特定内存位置的值。
如果可以使用左值改变对象中的值,该左值就是一个可修改的左值。而一般不可修改的左值有两类,一类是const修饰的变量,另一类是字面量。
可以用存储期(生命周期)描述对象,指的是对象在内存中保留了多长时间。标识符用于访问对象,可以用作用域和链接描述标识符,它们表明了程序的哪些部分可以使用标识符。不同的存储类别具有不同的存储器,作用域和链接。
标识符可以在源代码的多文件中共享、可以用于特定文件的任意函数中、可以仅限于特定函数中使用,甚至可以只在函数中的某部分使用。
对象可存在于整个程序的执行期,也可以仅存在于它所在函数的执行期,这取决于它的存储期。
我们先学习一波作用域、链接和存储期,再具体介绍存储类别。
作用域
作用域描述的是程序中可访问标识符的区域,通过标识符访问变量属于直接访问,作用域也就是变量能够直接被访问的区域。
你可能会想到这么一个问题:如果超出了作用域的话还能访问变量吗?这个问题的答案我们在后面会揭晓,先保留这么一个疑问继续往下看。
在C中,作用域有以下几类:
块作用域
函数作用域
函数原型作用域
文件作用域
块作用域
块是用一对花括号{ }括起来的代码区域。例如,整个函数是一个块,而函数中的任意复合语句也是一个块。
定义在块作用域的变量具有块作用域,其可见范围是从定义处到包含该定义的块的末尾。
我们使用的局部变量(包括函数形参)都具有块作用域
示例:
int add(int x, int y)
{
return x + y;
}
int main()
{
int a = 10;
if (10 == a)
{
int b = 5;
for (int i = 0; i < b; i++)
{
char ch = 'k';
printf("%c ", ch);
}
printf("%d ", add(a, b));
}
return 0;
}
分析一下:
定义在内层块里面的变量,作用域仅限于其所在块,也就只有内层块中的代码才能访问。
以前,具有块作用域的变量都必须在块的开头定义,C99标准放宽了这一限制,允许在块中的任意位置声明变量。因此可以在for()的圆括号内的条件初始化的位置定义变量。
函数作用域
仅用于goto语句的标签,这意味着一个标签无论在函数内的任何地方出现,其作用域都延伸至整个函数,并且不可跨函数。
函数原型作用域
用于函数原型中的形参,从形参定义处到原型声明结束。
比如:
int mighty(int, double);
编译器在处理函数原型的形参时只关心它的类型,而形参名通常无关紧要,即使有形参名也不必与函数定义中的形参名相匹配。
文件作用域
变量定义在函数外面,具有文件作用域,范围从它的定义处到该定义所在的文件末尾,也就是说可以在该文件内任何地方该变量均可见,均可使用该变量。
由于这样的变量可用于多个函数,我们称之为全局变量。
关于翻译单元
通常在源文件中会包含一个或多个头文件,而头文件又会依次包含其他头文件,所以实际上会包含多个单独的物理文件。C的预处理是用包含的头文件内容来替换#include指令,所以编译器把源文件和包含的所有的头文件合起来看成是一个包含信息的单独文件,也就是翻译单元。
描述一个具有文件作用域的变量时,它的实际可见范围是整个翻译单元,而如果程序是由多个源文件组成的,那么这个程序也将由多个翻译单元组成,每个翻译单元均对应一个源文件和它所包含的头文件。
链接
链接简单来说就是将源文件编译生成的目标文件与链接库进行链接。
C变量有三种链接属性:
外部链接
内部链接
无链接
具有块作用域、函数作用域或函数原型作用域的变量都是无链接变量,这意味着这些变量属于定义它们的块、函数或原型独有。
具有文件作用域的变量可以是外部链接或内部链接。
外部链接变量可以在多文件程序中使用也就是可以在多个翻译单元中使用,内部链接变量只能在一个翻译单元中使用。
如何知道文件作用域变量是内部链接还是外部链接?看其定义时是否被存储类别关键字static修饰,比如
int giants = 6; // 具有外部链接属性
static int gogers = 5; //具有内部链接属性
总结一下:
作用域是单个翻译单元内的概念,而链接属性是翻译单元层面的概念,它们两个共同决定变量的可见性,也就是说决定在哪些地方可以使用该变量。
以上就是本文全部内容,感谢观看,你的支持就是对我最大的鼓励~