C语言之存储类的相关的关键字
不同的数据在内存中的存储位置是不同的,总体来说内存中存储数据的地方主要有四部分:栈、堆、数据段、bss段,这些地方分别存放着不同的数据,比如栈一般用来存储局部变量,堆内存需要程序员字自己申请以及释放,主要用来存放比较大的数据;数据段主要用来存放显示初始化的全局变量和static关键字修饰的静态局部变量;bss段一般用来存放未显式初始化的全局变量或显式初始化为0的全局变量(C语言中,默认全局变量初始化为0)。C语言还提供了一些关键字来修饰变量,使其附有其他的属性,这些关键字主要有:auto、static、const、register、extern、volatile、restrict。
1:auto
auto关键字的作用只有一个:修饰局部变量,auto关键字修饰后的局部变量表示是自动局部变量,自动局部变量是分配在栈上面的,C语言中默认局部变量就是自动局部变量。
2:static
static关键字的作用有两种:
第一种:修饰局部变量,得到静态局部变量,静态局部变量是被分配在数据段或者bss段上面的,而非静态局部变量是被分配在栈上面的。
第二种:修饰全局变量,得到静态全局变量,静态全局变量和非静态全局变量的比较如下:
(1)静态全局变量和全局变量的存储类一样,都是被分配在数据段或者bss段
(2)静态全局变量和全局变量的生命周期一样,
(3)静态局部变量的作用域是代码块作用域({}:为一个代码块,普通局部变量一
样),链接属性是无连接;全局变量的作用域是文件作用域(和函数一样),链接属性是外连接。
3:const
const常被称为常量修饰符,也就是说const修饰后的“变量”变成了常量,(所以在函数传参时常用const来修饰输入型参数,不用const修饰的参数我们常认为是输出型参数。)但是在C语言中,这种常量也不算是完全不可变的,因为const修饰后的“变量”并不是被放到代码段(代码段是只读的,数据被放到代码段就无法修改),const关键字修饰的“变量”通过指针的方式还是可以修改的,具体实现是通过指针指向这个“变量”,然后解引用就可以修改const修饰后的“变量”。
4:register
register关键字不常用,它的作用也是唯一的:register修饰的变量,将来在编译的时候编译器会尽量将它分配到寄存器中(平时一般的变量都是被分配到内存中的),变量被分配到寄存器中其使用和被分配到内存中是一样的,但是其速写的效率会高很多。但是由于寄存器的数量是有限的,所以这里只是尽量放到寄存器中,而不是一定。我们平时需要用register修饰的变量很少,所以需要慎用。
5:extern
extern关键字是用来声明全局变量的,原因是C语言中编译的时候是以单个.c文件为单位来进行编译的,但是一个大的工程中不可能只有一个.c文件,这个时候就需要extern将变量,函数等声明要.c文件外部,这样编译的时候a.c中的函数和变量就可以被b.c所调用。
6:volatile
volatile修饰的变量表示这个变量随时可以被修改,因此编译后的程序每次需要存储或者读取这个变量时都会直接从变量地址中去读取数据,而没有volatile修饰的变量,则编译器可能会优化读取和存储,可能暂时使用的就是寄存器中的值,如果这个变量被其他程序修改了的话,编译器就有可能认为这个变量仍然没有被修改(原因是读取的可能是寄存器里面的那个副的变量值,而程序修改的是内存中变量的值)
int flag;
void test(void)
{
do1();
while(flag==0);
do2();
}
只有当flag不为0时,d02()函数才能被被执行,假设现在我们通过按下按键来产生中断,然后再中断处理函数中将flag赋值为1,这样d02()函数就能被执行了,但是编译器并不知道flag的值被其他程序所修改了,原因是编译器在编译的时候会对程序进行优化,这样的优化会可能会将flag放入寄存器,下次读取的时候就可能的是寄存器中的flag的那个值。
一般来说,volatile主要用在下面几个地方
(1)中断服务程序中修改的供其他程序检测的变量
(2)多任务环境下各任务间共享的标志应该加volatile
(3)存储器映射的硬件寄存器通常也要加volatile进行说明,因为每次对他们的读写可能不同。
简单来说,volatile总是和优化有关,编译器有一种技术叫做数据流分析,分析程序中的变量在哪赋值、在哪使用、在哪失效,分析结果可以用于常量合并,常量传播优化,进一步可以死代码消除,但是有时候程序不需要这些优化,这时候就可以通过volatile来禁止这些优化。
7:restrict
restrict关键字只能用来修饰指针,不能用来修饰变量,具体用法可参照下文
c99中新增加了一个类型定义,就是restrict。
看了下网上的相关贴子,但还是问题解决的不够。下面是相关一个文章,我将在后面再加相关说明:
那么restrict的意义是什么呢?
概括的说,关键字restrict只用于限定指针;该关键字用于告知编译器,所有修改该指针所指向内容的操作全部都是基于(base on)该指针的,即不存在其它进行修改操作的途径;这样的后果是帮助编译器进行更好的代码优化,生成更有效率的汇编代码。
举个简单的例子
int foo (int* x, int* y)
...{
*x = 0;
*y = 1;
return *x;
}
很显然函数foo()的返回值是0,除非参数x和y的值相同。可以想象,99%的情况下该函数都会返回0而不是1。然而编译起必须保证生成100%正确的代码,因此,编译器不能将原有代码替换成下面的更优版本
int f (int* x, int* y)
...{
*x = 0;
*y = 1;
return 0;
}
啊哈,现在我们有了restrict这个关键字,就可以利用它来帮助编译器安全的进行代码优化了
int f (int *restrict x, int *restrict y)
...{
*x = 0;
*y = 1;
return *x;
}
此时,由于指针 x 是修改 *x的唯一途径,编译起可以确认 “*y=1; ”这行代码不会修改 *x的内容,因此可以安全的优化为
int f (int *restrict x, int *restrict y)
...{
*x = 0;
*y = 1;
return 0;
}
最后注意一点,restrict是C99中定义的关键字,C++目前并未引入;在GCC可通过使用参数" -std=c99"
来开启对C99的支持
下面是我从C语言核心技术一书上摘的:
void *memcpy( void * restrict dest ,const void * restrict src,sizi_t n) 这是一个很有用的内存复制函数,由于两个参数都加了restrict限定,所以两块区域不能重叠,即 dest指针所指的区域,不能让别的指针来修改,即src的指针不能修改. 相对应的别一个函数 memmove(void *dest,const void * src,size_t)则可以重叠。