C语言有几个关键字,在定义一个变量或者一个函数的时候,指定其存储区域类型,被称为存储类关键字,它们是:
static,extern,register 和 auto
下面逐一讲解。
1,static
其实这个关键字有三个作用,而不仅仅是存储类型。请看下面代码:
- // 1.修饰函数,使其只能在本文件可见
- static void func(void)
- {
- static int n = 0; // 2. 修饰局部变量,使其存储在静态区(存储类型)
- printf("%d\n", n);
- }
- static int global; // 3. 修饰全局变量,使其只能在本文件可见
2,extern
extern关键字用来修饰一个标识符(变量或者函数)的声明,表明该标识符在“别的地方”有定义,参考以下代码:
- extern int a; // 声明一个“别处”定义的全局变量,此处extern不可省略
- extern int f(); // 声明一个“别处”定义的函数,此处extern可省略
3,register
顾名思义,register用来修饰一个希望存放在寄存器中的变量,但是这仅仅是对系统的一个建议,寄存器是一种稀缺资源,能不能满足要求要看系统当时的情况。但是,不管系统是否真的将变量存入寄存器,程序都不能对该变量进行取址运算。另外,并不是什么变量都可以定义为register类型,因为寄存器的大小跟CPU的字长相关,一般在32位系统当中,寄存器也是32位的,因此可以将小于等于32位的变量声明为register型。
被声明位register类型的变量一般是因为程序对其读写性能很敏感,比如在底层操作系统中,要频繁地对某个变量进行读写,而且这个变量的读写速度直接影响到了程序的性能,是整个程序的热点也是瓶颈,那么就很有必要将其声明为register存储型。
4,auto
auto这个关键字就像signed一样,一般都会被省略。因为auto的含义是将一个变量存储在“自动”存储区,所谓的自动存储区就是进程的栈空间,而普通的局部变量默认就是存储在 栈空间的,所以没有必要再用auto来修饰。而auto又不能修饰全局变量,因为全局变量一定是存储在静态区的。请看下面的分析:
- // 所有的全局变量统统存储在静态区
- int g1;
- static int g2; // static 跟存储类型无关,它改变的是g2的可见范围。
- int main(void)
- {
- int a; // 不需要auto修饰,默认就是自动变量,存储在栈空间
- static int b; // 被static修饰,存储在静态区
- }
下面是每一种变量在内存空间中的分布情况:
注意上图中的用户栈和运行时堆,它们是会在程序运行时动态变化的,里面的变量时而诞生时而消亡,具体而言指的是:在栈中存在的变量都是局部变量(包括函数的形参),其生命周期是从定义它的语句开始,到离开其作用域为止。在堆中存在的变量都是所谓的动态内存,这些内存的生命周期都是由程序员指定的,从malloc()分配某块内存开始,到free()释放这块内存结束。堆和栈分别向上和向下的箭头代表它们在内存中的增长的趋势。
相对地,读写段(即静态区)、代码段、常量区等内存区域则在程序运行期间是“稳定”的,即不会被释放,这些区域的内容将会被一直保留,直到整个程序退出为止。
Note:
1. static 修饰函数,全局变量,则只允许在本文件中可见,而修饰局部变量,则使其存款在静态存储区,在函数调用完了过后,值仍然存在,而再一次调用函数的时候, 从刚才离开的值开始计算。
2. extern关键字用来修饰的变量或函数,说明在其他文件中定义了。或是在本文件中其他地方定义了。如先用extern int SUM, 然后,在本文件的接下来的函数,用到了SUM,这时,不需要定义,直接用,而在最后定义SUM.
3. 经常用到的变量,定义为register类型,而register类型的变量,是不能取其地址的。
4. auto变量,存储在栈中(32位系统,一般为8M),而malloc的变量,则存储在堆中,栈是有限的, 栈的地址从大到小,而堆的地址是从小到大。.data段放已初始化的变量,而.bss段放未初始化的变量, 代码段和静态区的地址,比堆更小。
5. 动态内存用过需要释放,而不释放,会造成内存泄漏,这是某些服务器,一段时间需要重启下的原因。
这里来着重强调一下堆和栈。
对于栈而言,要明确的一点是栈空间的大小是有限的,通常来讲,对于一个32位的系统而言,一个进程的默认栈空间是8M,如果是嵌入式LINUX系统,其栈空间的大小可能经过调整会更小,比如1M。(如果是多线程,多个线程的栈空间会一字排开,但是中间用零权限内存来构建一个警戒区加以保护)。而栈空间存放的是局部变量,因此,以下情况不适合使用局部变量:
第一,巨大的变量。比如一个2000个元素的数组,那么大的数据量很有可能使得栈溢出。
第二,动态的数据。比如一个动态增长的链表,虽然程序运行初时不大,但是程序无法预料以后该链表的节点数目,像这种动态的数据也不能用栈空间来存放。
由于栈空间的分配跟释放是由系统自动完成的,其执行效率高速度快,所以适合用来存放一些可以被临时释放的数据。
而堆空间,则是程序运行时大部分数据的真正的演练场,这部分空间的分配和释放完全由程序员来控制,因此也叫做内存的自由区,自由意味着谨慎,如果只分配不释放,内存就会很快被用光,因此,堆空间是一个需要你更加负责任的地方,请看下面的代码:
- char * func(void)
- {
- char *p = malloc(100); // 在堆空间中申请一块100bytes的内存
- return p; // 返回后,堆内存不会因为函数的退出而释放
- }
- int main(void)
- {
- char *k;
- k = func();
- if(k == NULL)
- return -1; // 堆空间申请失败!
- ... // 堆空间申请成功,并使用这块内存
- ...
- free(k); // 释放这块内存
- }
动态内存的使用有几点要注意:
1,使用完毕之后,一定要释放。
2,如果不释放,那么该块内存只能等到程序全部退出之后才能被释放。
3,free() 只能释放动态内存(即由malloc( ) 或者 calloc() 或者 realloc() 分配的内存)。
4,访问动态内存的唯一方式是采用指针,因为动态内存是匿名的。
以上代码中的第3行,用一个char型指针来索引这块动态内存,是因为char型指针可以很方便地遍历内存中的每一个字节。如果这块内存是用来存放特殊的数据类型,就用需要的类型指针来遍历即可。