关键字:auto, extern, static,const
函数:malloc(),free()
主要内容:变量的作用域(可见的范围)和生命周期(它存在多长时间)
存储类别总结
C语言中,有5种存储类别(不包括线程)。下面先定义,再慢慢解释具体的概念。
自动:
无特殊说明或者带auto说明符声明的变量,自动存储期,块作用域,无链接。如果未初始化,其值未定义。
静态,外部链接:
在所有函数外部并且 没有 使用 static 来声明变量是静态的,静态存储期,文件作用域,外部链接。只能被编译器初始化一次。如果未初始化,其值默认为0。外部链接的意思是,其他 .c 文件 可以 通过extern来调用该文件中定义的此变量。
静态,内部链接:
在所有函数外部并且 有 使用 static 来声明变量是静态的,静态存储期,文件作用域,内部链接。只能被编译器初始化一次。如果未初始化,其值默认为0。外部链接的意思是,其他 .c 文件 不可以 通过extern来调用该文件中定义的此变量。
静态,无链接:
在某块中被 static 说明符声明,静态存储期,块作用域,无链接。只能被编译器初始化一次。如果未初始化,其值默认为0。
寄存器变量:
未使用过,暂不介绍。
概念解释
存储期
存储期表示一个变量在内存中存在了多长时间。用作用域和链接去描述标识符(诸如static, const, extern)。
作用域和链接
作用域和链接可以表明,这个变量可以被程序的哪个块,哪个函数,哪个文件所调用。
概念详解
自动:
学习编程期间,我们见到的大多数变量都是自动变量。它在 块中(任意一对花括号中间)被定义,并且无static声明。自动变量只在被定义的块中有效。它在程序执行到块中声明语句时被创建, 它在函数进入块时被分配内存,在该块结束时被自动释放。
静态:
static变量恐怕是很多初学者的FAQ,其实了解后就会发现它的定义非常简单。那就是static的变量,在程序被载入到程序结束期间都存在,当程序结束时被自动释放(即使该static变量是在某块中被初始化,它占用的内存不会在块结束时被释放。)
外部,内部,无链接:
这三个概念其实也非常简单。当一个变量像我们include stdio.h 一样,定义在所有函数外部,那么这个变量就是有链接的。如果他同时被static声明,那么它是内部链接,内部链接不能被其他 .c 通过extern访问,我们可以理解成只在该文件内的 “全局变量”。相反,如果没有static声明,那么该文件可以被其他 .c文件通过extern访问,它是所有文件都能用的“全局变量”。而静态无链接变量,这种储存类型的文件只能在被定义的块中被访问,换句话说,此变量的作用域是定义它的块。
存储类别异同和注意事项
自动变量在被初始化时,它的值会继承这块内存上一次储存的值(如果存在),总之你别期待着它自动变成 0 。但是如果我们定义 static 变量时,没有给它初始化,那么它的值默认为 0 。如果变量是数组名,那么所有数组成员都为0 。
一般我们在创建自动变量时,不需要加上 auto,一般加auto是强调,我需要创建一个和 static 变量同名的变量名,来特指。个人建议没事闲的别建同名的变量。
块作用域static变量举例,比如for 循环中的 static 声明,例如:
for (int i = 0; i < 2; i++) {
int x = 1;
static int y = 1;
printf("x is %d, y is %d", ++x, ++y);
}
可以设想一下,这个函数运行的时候printf都会输出什么,
答案是,x是自动变量,自动变量在块结束时都会被释放。
每次进入for循环块,x都会被重新声明为1,所以printf的第一项永远为2,
但是static int y = 1这条语句在程序开始时就被分配内存。
static int y = 1; 这条声明实际上不是for的一部分。
如果开启调试模式,你会发现,程序似乎 跳过 了这条声明。
因为静态变量和外部变量在程序被载入时内存已执行完毕。
把这条语句放在for里面是为了告诉编辑器, 只有这个for循环 才能 “看到” 该变量。
**这条声明未在运行时执行。**
-
什么时候需要用到extern?
如果一个变量定义在另一个文件内,而我们要在自己的文件内使用它,那么必须使用 extern 来声明,我使用了其他文件中的 静态外部链接 变量。
声明了外部变量,可以在函数中使用extern来强调,我接下来要用的变量是外部变量。
int num;
int arr[10];
int main(void){
extern int num;
extern int arr[];
}
这里其实完全可以省略 extern int num; extern int arr[]; 这两条代码,
它们仅仅是为了强调这两个变量是全局变量。
但是要删除的话就全删除掉, 如果只删除了 extern,那么实际上你是建立了两个同名的自动变量。
我们正常创建的函数,都是外部定义的,所以这些函数都可以被其他 .c中使用 extern调用。同时函数也可以被静态定义,静态函数只能在该文件内使用。
外部变量只能初始化一次,且必须在定义变量时进行,并且初始化只能使用常量表达式。
malloc和free
malloc函数接收一个参数,那就是大小,返回一个参数,代表生成内存的首字节地址。把这个地址赋值给一个指针变量,指针就可以访问这个内存了。
malloc生成的内存是void类型的,所以在使用的时候尽量使用强制类型转换。
int *res = (int *)malloc(sizeof(int) * ASize); // (刷过leetcode懂得都懂。。。)
free函数的参数,是之前malloc返回的地址。既然是地址,我们就没必要非得free当时malloc赋值给的那个指针,只要你free参数的指针,指向的是那个地址就行。
老生常谈的问题,为什么一定要free。假设你在for循环中malloc并指向一个自动变量指针,如果没有在for循环中free,那么这个自动变量在每次块结束的时候就被释放,相当于你就再也找不到这个地址了,想释放都释放不了。那么循环1万次,每次都无法释放,让冗余的数据占据内存,之后很可能就堆栈溢出了。这类问题就叫内存泄漏(Memory Leak)。
存储类别和动态内存分配
这个部分详细说的话,可以再写一篇博客了。所以这里只说结论。
Malloc生成的变量,存放在堆。
自动变量,都存放在栈上。
堆和栈加起来,叫动态存储区,这个地方的内存一会变大一会变小,所以是动态的。
静态 、常量、全局变量,存储在静态存储区,也叫全局存储区。
堆和栈的五大区别:
(我在百度上一搜,反而是一个广告网站里总结的挺好,就不附上链接了怕广告吓到你。。。)
申请方式不同。栈由系统自动分配,堆是程序员认为申请开辟。
申请大小不容。栈获得的空间小,堆获得的空间大。
申请效率不同。 栈由系统自动分配,速度快,堆速度较慢。
存储内容不同。 栈在函数调用时,函数调用语句的下一条可执行语句的地址第一个进栈,然后函数的各个参数进栈,静态变量不入栈。堆一般在头部用一个字节存放堆的大小,堆中内容人为安排。
底层不同。 栈是连续的空间,堆是不连续的空间。
const类型限定符使用
类型限定符一共有三种: const, volatile,restrict. 由于 const 最常用并且我还没有用到过其他两个限定符,就先专门总结一下const吧。
const 的目的是让被定义的变量无法被改变,听起来挺简单的,不过很多使用的时候也会有一些小坑。
const声明指针时候的位置
const int *p;
int * const p;
const int * const p;
最上面两条代码是有区别的。关注点在 const 是在 * 左边还是右边。
第一条代码声明了一个 int 型指针变量,p解引用(*p的值)是不能被改变的。但是,p可以指向其他的值。
第二条代码声明了一个固定地址的p,即该指针只能指向这个地址,但是这个地址上面的值是可以被改变的。
第三条代码当然是全部都固定住不能改变。
const in *p = NULL;
*p = 5; // 这里会报错: Read-only variable is not assignable
虽然解引用(*p的值)是不能被改变的。但是,p可以指向其他的变量,解引用获得其他的值。
int main(void)
{
int a = 5;
const int *p = NULL;
p = &a;
printf("%d", *p);
}
对全局数据使用const
外部链接的全局变量可能被其他文件 extern调用,所以为了防止他们被意外更改,还是加上const较好。
同时如果为了省事,我们可以把 const double PI = 3.14
这种语句写在头文件里面,然后在其他 .c 文件中 extern就好了。
但是这里也有坑,那就是写在 头文件 中的全局变量声明,最好加上 static ,因为如果不加,那么每个 include 了这个 头文件的 .c文件,都会多出一句这个代码 : const double PI = 3.14
而且这种声明都是外部链接啊,那到时候万一有的文件没有 include 这个 头文件,而是选择 extern 这个变量, 那么编译器怎么知道它到底想要的是哪个 PI?
其实C标准是不允许这么做的,所以写在头文件里面的东西,最好还是加上 static,要不然容易出问题。