类型限定词

在本系列的第二篇文章 中,我曾经提到说,在C语言中,一个变量所包含的的属性信息并不仅仅是之前说到四种属性(类型信息存储时期作用域链接),它还有着其他的属性,本文主要就是分析一下变量的这些其他属性信息:

  • 不变性(constancy)
  • 易变性(volatility)

这两个属性是通过关键字constvolatile来声明的,通过这两个关键字的修饰就产生了受限类型(qualified type)

另外,C语言中还有第三个限定词restrict,这个限定词是用来进行编译器优化的。

最后想要说明的是,这三个类型限定词是幂等(idempotent)的,也就是说,可以在一个声明中不止一次地使用同一个限定词,多余的限定词将被忽略掉。

const

const修饰的变量是不能通过赋值、增量或减量运算来修改该变量的值,这样的变量的值只能在声明的时候被定义,此后在任何情况下都不能被修改了。简单来说,被const修饰的变量就是一个常量

粗略来看,C语言中的宏定义也可以作为常量来使用,对于这一点我印象比较深刻,记得当时老师讲到这一部分的时候,强调说,在程序需要用到常量时,应该尽量使用const而不是宏定义,这是因为宏定义只是简单的替换操作,如果在程序多次引用定义的宏的话,会在内存中出现多个具有相同值的内存空间,会造成内存浪费;而使用const的话,数据是保存在符号表中的,作为常量来处理,并不会占用堆栈区中的内存空间,这样就避免了多次访问内存,同时又能被多次引用了。很明显这要比使用宏定义来得优越。

指针的const

记得当时学习的时候,用const来修饰指针的时候,是最让人头晕的,因为const放置位置的不同,它会修饰不同的数据。

#!c
const float * pf //pf指向一个常量浮点数数值

float * const pt //pt是一个常量指针

const float * const ptr

上述代码中的const,修饰的是不同的数据,第一行的声明,说明了指针pf指向的数据是一个常量,而指针pf本身1是可以改变的。与此相反的是,第二行的声明,说明了指针pt本身是一个常量,是不可变的,只能指向同一个内存地址,但是它所指向的内存地址中的数据却是可以改变的。 第三行的声明就比较好理解了,它说明了指针ptr本身是不可变的,被它所指向的地址中的数据也是不可变的。

另外,还有一种写法:

:::c
float const * pfc //等同于 const float * pfc

上述的写法等同于const float * pfc

每当这时候,总会觉得很混乱,const的不同用法该如何去记忆,其实有一条简单的规则就能搞定一切:

位于*左边任意位置的const使得数据成为一个常量,而一个位于*右边的const使得指针自身成为一个常量。

volatile

一般来说,编译器为了加速的程序的执行,会对程序进行一些优化操作,最常用的方式就是: 将内存变量放入到寄存器中,调整指令顺序,充分使用CPU的流水线。 大部分情况下,这些优化操作是非常有效的,但是在一些少数情况下,这样的优化方法会导致程序错误,这时,就需要用volatile关键字来显示地告诉编译器,被volatile修饰的变量不需要这样的优化,每次都需要从内存中读取数据

那么,什么样的情况下,编译器的优化操作是失效了,可以设想一下,在一个多线程的程序中,某个变量是被多个线程共享的,假设线程A引用这个变量时,这个共享变量就被加载到寄存器中缓存起来了,但是另外一个线程B却修改了其在内存中的数值,当线程A再次引用这个变量时,它引用的 是寄存器中被修改之前的数据,这就会引起程序的错误。volatile关键字就是告诉编译器,这个变量是"易变"的,随时可能被其他外部程序所修改,所以不应该进行寄存器缓存的优化操作。

volatile修饰的变量每次被引用时,系统总是会到其内存地址中重新读取数据。

restrict

关键字restrict主要也是用来方便编译器进行优化的,它只能使用于指针变量,并且表明该指针是访问一个数据对象唯一且初始的方式。

在不适用restrict的情况一下,一个指针指向的地址也有可能被其他指针所指向,因此编译器不能确定该快内存地址中的数据是否被改变了,也就不能进行一些优化操作。比如下面的例子:

#!c
int ar[10];
int * restrict restar = (int *)malloc(10 * sizeof(int));
int * par = ar;
int i;
for (i = 0;i < 10;++i)
{
    par[i] += 5;
    restar[i] += 5;
    ar[i] *= 2;
    par[i] += 3;
    restar[i] += 3;
}

在上述的代码中,restar被指定为访问它所指向的内存块的唯一初始方式,此时编译器就可以用同样效果的一条语句来替换关于restar的两条语句:

:::c
restar[i] += 8;

ar却无法进行这样的优化,因为指向它所指向的内存空间的指针不只一个,编译器不能保证内存块中的数据没有被修改过。使用了restrict关键字之后,编译器就可以放心地寻找计算的捷径了。


  1. 指针本身也是变量,只不过它存储的是内存地址。 

Share on: TwitterFacebookEmail


Flyaway is the owner of this blog.

Published

Category

programming-language

Tags

Contact