C语言中的const,volatile,restrict用法总结

const, volatile, restrict是我们在程序中经常遇到的几个限定词,是时候好好总结一下了。

本文转自http://www.jb51.net/article/42348.htm

  • const
    变量声明中带有关键词const,意味着不能通过赋值,增量或减量来修改该变量的值,这是显而易见的一点。指针使用const则要稍微复杂一点,因为不得不把让指针本身称为const和指针指向的值称为const区分开来。下面的声明表示pf指向的值必须是不变的。 const float *pf;而pf则是可以改变的,它可以指向另外一个const或非const值;相反,下面的声明说明pf是不能改变的,而pf所指向的值则是可以改变的,float* const pf。最后,当然可以有既不能改变指针的值也不能改变指针指向的值的声明方式const float * const pf。值得注意的是还有第三种防止const的方法,float const * pf;//等价于const float *pf
    总结就是一个位于*左边任意位置的const使得数据成为常量,而一个位于*右边的const使得指针本身成为常量。
    还要注意一点的是关于const在全局变量中的使用:
    使用全局变量被认为是一个冒险的方法,它使得数据在程序中的任何部分都可以被错误的修改,如果数据是const,那么这种担心是多余了不是吗?因此对全局变量使用const是合理的。然而在多个文件中使用const要注意。一个是遵循外部变量的惯用规则,在一个文件进行定义声明,在其他文件进行应用声明(使用extern关键字)。
/*file1.c----定义一些全局常量*/
const double PI = 3.14159;
/*file2.c----是用在其他文件中定义的全局变量*/
extern const double PI;

另外一个方法是把全局变量放在一个include文件中,这时候需要格外注意的是必须使用静态外部存储类。

 /*constant.h----定义一些全局常量*/
 static const double PI = 3.14159;
 /*file1.c----使用其他文件定义的变量*/
 #include"constant.h"
  /*file2.c----使用其他文件定义的变量*/
 #include"constant.h"

如果不使用关键字static,在文件file1.c和file2.c中包含constant.h将导致每个文件都有统一标识符的定义声明,ANSI标准不允许这样做(有些编译器确实支持)。通过使用static,实际上给每个文件一个独立的数据拷贝,如果文件想使用该数据与另外一个文件通话,这样做就不行了,因为每个文件智能看见它自己的拷贝,然而由于数据是不可变的,这就不是问题了。使用头文件的好处是不必在一个文件中进行定义声明在另一个文件中进行引用声明,缺点是复制了数据,如果常量很大的话,这就是问题(不是很能理解)。

  • volatile
    限定词volatile告诉编译器,该变量除了可以被程序改变以外还可以被其它代理改变。典型的它应用于硬件地址和其它并行运行的程序共享的数据。例如,一个地址可能保存着当前的时钟信息。不管程序做些什么,该地址会随时间改变。另一种情况是一个地址用来接收来自其它计算机的信息。
    语法同const。
volatile int a;//a是一个易变的位置
volatile int *pf;//pf指向一个易变的位置

把volatile作为一个关键字的原因是它方便编译器优化。
假如有如下代码:

va = x;
// 一些不使用x的代码
vb = x;

一个聪明的编译器可能注意到你两次使用了x,但是没有改变它的值,它将把x临时存储在一个寄存器中。当vb要用x的时候,它从寄存器而非初始的内存位置得到x的值来节省时间。这个过程被称为缓存。通常缓存是一个好的优化方式,但是如果中间的其它代码改变了x的值就不是这样了。如果没有规定volatile关键字,编译器将无从得知这种改变是否可能发生,因此为了安全起见,编译器不使用缓存。但这是ANSI以前的情形。现在,如果声明中没有使用volatile关键字,编译器就可以假定一个值在使用过程中没有修改,它就可以试着优化代码。总而言之,volatile使得每次读取数据都是直接在内存中读取而不是缓存。
你可能会觉得奇怪,const和volatile可以同时使用,但是确实可以。例如硬件始终一般不能由程序改变,这使得它成为const,但它被程序以外的代理改变,这使得它成为volatile,所以你可以同时使用它们。顺序不是很重要。
volatile表明某个变量的值可能在外部被改变,优化器在用到这个变量时必须每次都小心的重新读取这个变量的值,而不是使用保存在寄存器中的备份。它可以适用于基础类型,例如int,char,long…也可以用于C的结构和C++的类。当对结构或类对象使用C++的类使用volatile的时候, 结构或类的所有成员都会被视为volatile。
该关键字在多线程环境下经常使用,因为在使用多线程的时候,同一个变量可能被多个线程修改,而程序通过该变量同步各个线程。
示例代码:

DWORD __stdcall threadFunc(LPVOID signal)
{
   int *intSignal=reinterdivt_cast(signal);
   *intSignal=2;
   while(*intSignal!=1)
   sleep(1000);
   return 0;
}

该线程启动时将intSignal置为2,然后循环等待直到intSignal为1时退出。显然intSignal的值必须在外部被改变,否则该线程不会退出。但是实际运行的时候该线程不会退出,即使将它外部的值改为1,看一下对应的伪汇编代码就明白了:

mov ax signal
label:
if(ax != 1)
goto label

对于C编译器来说,它并不知道这个值会被其它线程修改。自然就把它cache在寄存器里面。C编译器是没有线程概念的,这时候就需要用到volatile。volatile的本意是指:这个值可能会在当前线程外部被改变。也就是说我们要在threadFunc中的intSignal前面加上volatile关键字。这时候编译器知道变量的值会在外部被改变,因此每次访问该变量的值会重新读取。所做的循环变为如下伪汇编所示:

label:
mov ax, signal
if (ax != 1)
goto label

注意一个参数可以是const同时是volatile,是volatile是因为它可能意想不到(汇编代码局部的意想不到,而不是程序员的意想不到)的被改变。它是const因为程序不应该试图去修改它。

  • restrict

关键字restrict通过允许编译器优化某几种代码增强了计算支持。记住它只能用于指针,并且表明指针是访问一个数据对象的唯一且初始的方式。为了清楚为何这样做,我们需要看一些例子:

int ar[10];
int * restrict restar = (int *)malloc(10*sizeof(int));
int * par = ar;

这里指针restar是访问malloc分配内存唯一而且初始的方式,因此声明了restrict。然而par既不是初始的也不是访问数组ar的唯一方式,所以不用restrict关键词。现在考虑下面这个更加复杂的例子,其中n是一个int。

for(n=0;n < 10; n++)
{
    par[n] += 5;
    restar[n] += 5;
    ar[n] *= 2;
    par[n] += 3;
    restar[n] += 3;
}

知道了restar是访问它所指向的数据的唯一初始方式,编译器就可以用具有同样效果的一条语句来包含restar的两个语句。

restar += 8; /*可以替换*/

然而将两个计算par的语句精简为一个则会导致错误。因为在par两次访问数据之间,ar改变了该数据的值。没有关键字restrict,编译器将不得不设想比较糟糕的那种形式,而使用之后,编译器可以大胆的寻找计算捷径。可以将关键字作为指针型函数参数的限定词使用。这意味着编译器可以假定函数内没有其他标志符修改指针指向的数据,因为可以试着优化代码,反之不然。来看一下C99标准下C库中的两个函数,它们从一个位置把字节复制到另一个位置。

void *memcp(void *restrict s1, const void * restrict s2, size_t n);
void *memmov(void *s1, const void *s2, size_t);

memcp要求两个指针的位置不能重叠,但是memov没有这样的要求。把s1,s2声明为restrict意味着每个指针都是相应数据的唯一访问方式,因此它们不能访问同一数据块。这满足了不能有重叠的要求。
关键字restrict有两个读者:编译器,它告诉编译器可以自由的做一些优化的假定。另一个读者是用户,它告诉用户仅适用满足restrict要求的参数。一般,编译器没法检查你是否遵循了这一限制,如果你蔑视它,就是让自己冒险。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值