目录
一、需要明白c语言中对象的一些属性
在介绍关键字作用之前先声明几个概念
1、C语言中内存分配
C语言中内存分为5个区,分别为栈区,堆区,静态存储区,常量存储区,代码区。
1.1 栈区(stack): 由编译器分配内存的区域,当编译器需要的时候分配内存,使用完后清理内存,存放变量的类型为函数的形参、局部变量等。
1.2 堆区(heap): 指那些由程序员手动分配释放的存储区,如果程序员不释放这块内存,内存将一直被占用,直到程序运行结束由系统自动收回,c语言中使用malloc,free申请和释放空间。
1.3 静态存储区(static): 静态的概念是在程序执行过程中变量的地址不会发生改变。全局变量和局部静态变量存放在这一块区域,当程序结束后由系统释放。其中初始化了的全局变量或者由static 修饰的全局或局部变量放在一个区域(DATA段),未初始化的全局变量放在BSS区。
1.4 常量存储区:常量字符串就是储存在这里的,储存在常量区的变量为只读变量。const修饰的全局变量也储存在常量区,const修饰的局部变量依然在栈上。
1.5 程序代码区:存放源程序的二进制代码。
2、变量的作用域
一个C变量的作用域由4种:块作用域,函数作用域,函数原型作用域,文件作用域。
2.1 块作用域:块是用一对花括号括起来的代码区,例如函数是一个块,函数中的复合语句也是一个块。在块内定义的变量具有块作用域,块作用域的可见范围是从定义处到包含该定义的块结束。如下面代码示例1中,变量 a,num,i,p都是块作用域。a的可见范围为整个函数,函数外不可见,num可见范围为从定义处到函数结束时结束,在for中也是有效的。i和p都是只在for循环中有效,在for循环的花括号后的函数区域p和i变量是不可见的。
//代码示例1
void fun1(int a)
{
a++;
int num=0;
for(int i=0;i<3;i++)
{
int p=i*a;
num += p;
}
p=2; //错误,p不可见
}
2.2 函数作用域:仅用于goto语句的标签,使内层块中的变量作用域为整个函数,避免在两个块中使用相同标签发生混乱。
2.3 函数原型作用域: 作用域范围是从形参定义处到函数原型处声明结束。这样函数定义和声明中函数的形参名只需要保证变量类型一致即可,而形参名可以不同。
//函数声明
int func(int a,int b);//函数形参可以与定义处不同
...
int main(void)
{
int q=0;
q=func(2,3);//调用函数,返回5
return 0;
}
//函数定义
int func(int m,int f)
{
return m+f;
}
2.4 文件作用域:在函数外定义的变量具有文件作用域。从定义处开始到文件末尾均可见。
3、链接属性
C变量种由3种链接属性:外部链接,内部链接,无链接。
翻译单元:一个c文件和它所包含的头文件为一个翻译单元。
3.1、无链接:具有块作用域,函数作用域或函数原型作用域(局部变量,函数形参等)都是无链接变量,无链接只能在块内可见。
3.2、内部链接:内部链接变量只能在一个翻译单元内可见。
3.3、外部链接:具有外部链接的变量可以在多个c源文件中使用(不同翻译单元都可使用),全局变量默认为外部链接,通过extern关键字在不同文件中引用。
4、存储期
C语言中对象有4种存储期:静态存储期、线程存储期、自动存储期、动态存储期。
4.1、 静态存储期:如果对象具有静态存储期,那么它在程序执行期间一直存在。文件作用域变量就具有静态存储期。
4.2、线程存储期:用于并发程序中,如果对象具有线程存储期,那么该对象从声明到线程结束中一直存在。程序的执行可以被分为多个线程,多个线程交替运行使人感觉多个程序同时运行。
4.3、自动存储期:块作用域具有自动存储期,当编译器使用到这些块时为它们分配内存,当块结束时清理掉这一部分内存,这一部分内存是可以反复使用的,自动存储期的变量一般保存在内存的栈中。
4.4、动态存储期:由程序员手动申请和释放的变量。如使用malloc()申请内存,free()释放内存,注意:free()只是将申请的内存收回到系统中,但是指针本身指向的地址并没有发生改变,所以释放地址后一般需要将指针的值改变(如指向NULL)。如果申请内存后不释放,该内存到程序结束前一直被占用,可能会造成大量内存浪费。
变量类别 | 存储期 | 作用域 | 链接 | 声明方式 |
普通局部变量 | 自动 | 块 | 无 | 在块内声明 |
静态局部变量 | 静态 | 块 | 无 | 在块内用static声明 |
全局变量(外) | 静态 | 文件 | 外部 | 在所有函数外声明 |
全局变量(内) | 静态 | 文件 | 内部 | 函数外用static声明 |
寄存器 | 自动 | 块 | 无 | 在块内用register声明 |
二、static、const、volatile关键字作用
1、static关键字作用
1.1对于局部变量
使用static关键字修饰局部变量,会改变变量的存储期,由自动存储期改为静态存储期。由程序开始时就分配内存并且初始化(如果程序没有显示的初始化,系统会默认初始化为0),当后面调用函数或块时不会再进行初始化,在程序结束时由系统清理内存。
1.2对于全局变量
使用static关键字修饰全局变量,会改变变量的链接属性,由外部链接改为内部链接,在其他c文件中该变量是不可见的。全局变量默认为外部链接,在不同的c源文件可以通过extern来使用相同的变量,但是当使用static修饰全局变量后,变为内部链接这样不同的翻译单元就不能使用到同一个变量,但是不同的翻译单元可以使用同一个变量名(变量名相同,并不意味着是同一个对象)。如下代码,在file1.c和file2.c中两个a不是同一个变量,但是两个f1为同一个变量。
//file1.h
//int b=0;//当两个c文件引用头文件存在这种代码,系统会提示错误,原因是同一个变量对象被声明了两次
static int a=0;//这样是能编译通过的,因为在两个c文件中虽然都有了a变量,但他们只是在各种的翻译单元中有效,相互之间没有关系
int fun2(void);//声明file2中定义的函数,在file1.c中可以使用
//file1.c
#include<stdio.h>
#include"file1.h"
int f1=7;//这个变量在file2.c中可以用 extern int f1;来声明该变量在其他文件中已经声明,来操作该变量
static f2=0;// 这个变量不能通过上诉操作来找到,因为它是内部链接
int main(void)
{
printf("file1中a的地址为:%p\n",&a);
printf("file1中f1的地址为:%p,f1=%d \n",&f1,f1);
fun2();
return 0;
}
//file2.c
#include<stdio.h>
#include"file1.h"
extern int f1;//声明在其他文件中已经定义,注意在这里只能引用示声明,不能赋值。
int fun2(void)
{
printf("file2中a的地址为:%p\n",&a);
printf("file2中f1的地址为:%p,f1=%d \n",&f1,f1);
return 0;
}
/*输出结果
file1中a的地址为:00007FF6953D9BB0
file1中f1的地址为:00007FF6953DC00C,f1=7
file2中a的地址为:00007FF6953D9BB4
file2中f1的地址为:00007FF6953DC00C,f1=7*/
1.3对于函数
当有static修饰函数时该函数的作用域也变成了仅在本翻译单元可见。在其他c文件中不能引用该函数,即使在头文件同声明也不行。没有static声明的函数在上一个代码示例中,在不同的c文件中可以引用。
//file2.h
static int fun3(int a,int b);
//file2.c
...
static int fun3(int a,int b)
{
return a+b;
}
...
//file3.c
#include "file2.h"
void quate()
{
int a=0;
a=fun3(1,1);//会报错,该函数没有定义,因为static修饰的函数其他翻译单元不可见
}
2、const关键字作用
const会让变量成为只读变量,初始化过后其值不能通过赋值,递增,递减来修改。一般用来对需要保护的数据使用如:int fun(const int arr[]){};避免在fun函数中修改arr数组的值。
const int a=0;
a=2;//不允许修改
const int arr[2]={1,2};//创建不允许修改的数组
//const修饰指针
int b=0;
int c=1;
const int *p1 = &b;
int const* p2 = &b;//const 在*前为常量指针,即指针指向一个常量,即 *p为常量,p为变量
*p1=2;//不能通过解引用方式修改,*p1是不可修改的左值
b=2;//可以直接修改变量
p1=&c;//可以修改指针指向的地址
int * const p2 = &b;//const在 * 后为指针常量,即该指针为一个常量,不能修改指向的地址,即 p为常量但是*p是变量
*p2=3;//可以修改指针指向内容的值
p2=&c;//不能修改指向的地址
const int * const p2 = &b;//指针的值和指向的地址都不能改变
3、volatile关键字作用
由volatile声明的变量不应该优化,告诉计算机该变量是可以改变的,每次读写该变量时都需要在变量内存地址中操作,而不应该优化在寄存器中。
volatile语法和const一样.
4、restrict
restrict声明的变量是允许编译器优化,提升程序的效率。它只能用于指针,表明该指针是访问某一数据对象的唯一且初始的方式。
5、register
用register修饰的变量,该变量保存在寄存器中,读写速度块,但是不能获取该变量的地址。