内存管理的内容我之前没接触过,所以这一章不算回顾,算是学习新知识新东西,很激动很好奇
合理地使用内存资源去存储数据本来就是设计程序的一个要点。
这一章我会学习如何通过指定变量的作用域(即可见范围)和生命周期,进一步控制程序。
文章目录
存储类别 storage class
对象
我们现在写的程序,数据都是存储在内存中的,在硬件层面看,每一个被存储的值都占用一定的物理内存,即对象。
之前说过,C语言的对象,object,指的是一块内存!不是面向对象编程语言的对象,那个对象是类的示例,它的定义包括了数据以及可对数据执行的操作两个方面。
C的对象可以存储一个值或者多个值(数组),或者不存也行(只声明,未赋值)。
程序通过左值访问对象(即 指定对象)
指定对象,即指定了那块内存(对象)中的值
用于指定对象的表达式被称为左值。
用于指定对象的表达式被称为左值。
用于指定对象的表达式被称为左值。
比如变量名
对象就是内存,访问对象就是访问内存
访问对象要通过左值, 具体可以分为2种:可修改的左值,如变量,指针表达式;不可修改的左值,如字面量
- 通过声明变量
int entity = 3;
entity是一个标识符,在这里用来指定特定内存中的值, 所以它也是左值。
- 通过指针
pt是标识符,是一个对象,它指向了存储entity地址的那块内存
∗ * ∗pt是一个表达式,它也指定了一个对象,且和entity指定的是同一个对象。所以 ∗ * ∗pt也是左值。
int *pt = &entity;
下面这句声明创建了一个可存储10个int类型的元素的对象。但是数组的每个元素都可以被单独访问,所以每个元素也是一个对象。
表达式 ∗ * ∗(rank+entity)也是一个左值,指向rank数组的第四个值。
int rank[10];
- 字面量(不可修改的左值)指定对象
"Behold a string literal!"字符串字面量指定了一块存储自己的对象,所以是一个左值,但是是不可修改的左值。
∗ * ∗st是一个可修改的左值,他也指向该字符串指向的对象,但是const指针可以指向别的内存,所以是一个可以修改的左值。
const char * st = "Behold a string literal!";
用作用域scope和链接linkage描述标识符
标识符的scope和linkage表示了程序的哪些部分可以使用它
标识符可以仅限于特定函数使用;或 用于特定文件的所有函数;或 共享于源代码的多个文件。
不同的存储类别有不同的存储期,作用域和链接
作用域(局部变量 VS 全局变量)
作用域描述了程序中可以访问标识符的区域,C的作用域分为4类:块作用域, 函数作用域, 函数原型作用域,文件作用域(外部链接文件作用域)。其中前三种作用域的变量是局部变量,具有文件作用域的则是全局变量。
其中,中间两种不是很重要,最后一种最重要。
块作用域 block scope
复习一下,块,是用花括号括起来的代码。比如函数体就是一个块,复合语句,比如if, while, for, do while语句也是一个块。
此外,函数的形式参数虽然写在花括号前,但是作用域仍是这个函数体。
形式参数是局部变量。
函数作用域 function scope
这种作用域只是针对goto语句的标识符,前面已经说过,不要用goto语句,,所以函数作用域就没那么重要了。
有函数作用域的标识符,即使被声明于函数的内层块里,作用域也是整个函数体。
函数原型作用域 function prototype scope
这个只是专用于函数原型的形参名,这些形参名在函数原型结束后就失效了,即函数原型作用域的范围是形参定义到函数原型结束,是一个very very small的作用域。
再加上我们之前说过,函数原型中写不写形参名并不重要,编译器只关注形参的类型,后面的函数定义中使用和原型不一致的形参名也完全没有问题。所以这个作用域,也没多重要。
但是有一个例外,那就是当形参是变长数组时,函数定义中的形参名必须和函数原型中的形参名一样!
外部链接文件作用域 file scope (全局变量)
在函数外面定义的变量,具有文件作用域。这种变量可以作用于多个函数,,所以具有文件作用域的变量也叫做全局变量。global variable
专业严格的说,具有文件作用域的变量的作用域是整个翻译单元,即当前这个源代码文件(.c)和它包含的头文件。
总结的说:
块作用域变量是定义它的块私有的;
函数作用域标识符是定义它的函数私有的;
函数原型作用域标识符是原型私有的
链接 存储类别说明符static
链接是针对具有文件作用域的变量而言的。
C语言的变量有三种链接属性:外部链接,内部链接,无链接。
前面说的四种作用域中,除了文件作用域以外的另外三种作用域的变量,都是无链接的。即局部变量都是无链接的,这很好理解,因为链接主要说的就是文件之间还是文件内部,讨论的是文件,所以局部变量当然不在讨论范围了。
而文件作用域变量可以是外部链接或者内部链接。或者说文件作用域又分为内部链接的文件作用域和外部链接的文件作用域。
内部链接的文件作用域:C标准给的术语,即仅限于一个翻译单元的作用域。程序员通常简称其为文件作用域;内部链接变量只可以在同一个翻译单元内使用。
外部链接的文件作用域:C标准给的术语,可延伸至其他翻译单元的作用域。程序员通常简称其为全局作用域或者程序作用域;外部链接变量 可以在多文件程序中使用。
关键字static用于说明文件作用域变量的链接属性,加static则具有内部链接。
存储期 storage duration
即对象在内存中存留多长时间
对象可以存在于程序执行期间;也可以只存在于它所在函数的执行期间;如果是并发编程,对象还可以只存在于特定线程的执行期间。
刚才说的作用域和链接描述了变量的可见性 ,现在了解存储期怎么描述这些标识符访问的对象的生存期。
存储期是针对对象的,即存储变量数据的一段内存。C的对象一共有四种存储期:静态存储期,线程存储期,自动存储期,动态分配存储期
静态存储期
有静态存储期的对象在程序运行期间一直存在。
文件作用域变量具有静态存储期,不管是外部链接还是内部链接。
线程存储期
用于并发程序设计,即程序的运行 被分为多个线程。
具有线程存储期的对象,从被声明到线程结束一直存在。
自动存储期
number, index变量在bore()函数被调用时才创建,调用结束就被销毁了。
块作用域变量也能具有静态存储期。
变量ct会被存储在静态内存中,静态内存中存储的数据从程序被载入到程序结束期间一直存在。但是作用域仍然是在这个函数中,只有调用这个函数时,程序才会用ct去访问它指定的对象,但是这个函数可以给其他函数提供这个存储区的地址以间接访问这个对象,比如通过指针形参或者返回值。
所以如果想让别的函数使用这个函数中的某个变量,可以在函数内部声明时加个static,把它放在静态内存中,就可以用返回值或者指针让别的函数访问了。
5种存储类别
前面说完了存储期,作用域,链接,通过这三个概念,我们可以为变量定义很多中存储方案,先不考虑并发编程涉及的存储类别,剩下5中存储类别
如果在所有函数外部声明的话,不加static也是存在静态内存中的;如果在块内声明的话,想存在静态内存中,则必须要用static
平时用的大多数变量都是自动存储类别的
自动变量 存储类别说明符auto
内层块隐藏外层块的同名变量
哈哈哈
while条件中的变量不属于while块
if条件中的变量不属于if块
for条件中的变量不属于for块,除非在for括号中声明哈
#include <stdio.h>
int main()
{
int x = 30;
printf("x in outer block: %d at %p\n", x, &x);
{
int x = 77;
printf("x in inner block: %d at %p\n", x, &x);
}
printf("x in outer block: %d at %p\n", x, &x);
while(x++ < 33)//打印3次
{
int x = 100;//覆盖了while条件中的x,因为while条件中的变量不属于while块
x++;
printf("x in while loop: %d at %p\n", x, &x);
}
printf("x in outer block:%d at %p\n", x, &x);
return 0;
}
x in outer block: 30 at 0061ff2c
x in inner block: 77 at 0061ff28
x in outer block: 30 at 0061ff2c
x in while loop: 101 at 0061ff24
x in while loop: 101 at 0061ff24
x in while loop: 101 at 0061ff24
x in outer block:34 at 0061ff2c
示例 for块
前面说了,while条件中的变量不属于while块
if条件中的变量不属于if块
for条件中的变量不属于for块,除非在for括号中声明哈
#include <stdio.h>
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 0061ff2c
loop 1: n = 1 at 0061ff28
loop 1: n = 2 at 0061ff28
After loop 1: n = 8 at 0061ff2c
loop 2: index n = 1 at 0061ff24
loop 2: n = 6 at 0061ff20
loop 2: index n = 2 at 0061ff24
loop 2: n = 6 at 0061ff20
After loop 2, n = 8 at 0061ff2c
寄存器变量(寄存器:最快的可用内存)
寄存器是最快的可用内存,对存在寄存器中的变量的访问和处理要快的多。
但是我们不能获取寄存器变量的地址哦,因为在寄存器中,不是内存。
大多数方面,寄存器变量和自动变量是一样的,都是块作用域,无链接,自动存储期。
用存储类别说明符register声明
块作用域的静态变量 (局部静态变量)
注意静态变量并不是指不能改变,而是说这个变量在内存中的位置不变, 原地不动。静态修饰的是存储期,存储类别说明符static就能给变量提供静态存储期。
具有块作用域的静态变量的声明已经说了,加个static就行
示例 静态变量根本不会在运行时声明!!!牛逼
#include <stdio.h>
void trystat(void);
int main()
{
int count;
for(count=1;count<=3;count++)
{
printf("Here comes iteration %d:\n", count);
trystat();
}
return 0;
}
void trystat()
{
int fade = 1;
static int stay = 1;
printf("fade = %d and stay = %d\n", fade++, stay++);
}
我被震惊到了,再次调用trystat函数时,stay并不会被重新赋值为1!!!
但是fade每次调用都会重新初始化,而static的变量只初始化一次。
我调试了一下,真的诶!!太牛了。我之前对程序的每条语句在运行时执行还是载入时执行非常好奇,总算接触到了
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
另外,如果不显示初始化局部静态变量,他们会被初始化为0(载入程序时)。