Sun Studio 12:C 用户指南

6.6 constvolatile

关键字 const 是 ISO C 所采用的 C++ 功能之一。而类似关键字 volatile 由 ISO C 委员会构造,从而创建了“类型限定符”类别。

6.6.1 类型(仅适用于 lvalue

constvolatile 属于标识符的类型,而不属于标识符的存储类。然而,当从表达式求值中获取对象的值时,确切地说是当 lvalue 变为 rvalue 时,经常会将这些类型从类型的最顶端删除。这些术语起源于原型赋值 "L=R",其中左侧必须仍直接引用对象(一个 lvalue),右侧只需为一个值(一个 rvalue)。因此,只有本身是 lvalues 的表达式才可以由 const 和/或 volatile 限定。

6.6.2 派生类型中的类型限定符

类型限定符可修改类型名称和派生类型。派生类型是 C 声明的那些可反复应用而生成越来越复杂的类型的部分:指针、数组、函数、结构和联合。除函数之外,可使用一个或两个类型限定符更改派生类型的行为。

例如,


const int five = 5;

声明并初始化类型为 const int 并且其值未被相应的程序更改的对象。关键字的顺序对于 C 并不重要。例如,声明:


int const five = 5;


const five = 5;

与以上声明在效果上相同。

声明


const int *pci = &five;

声明一个类型为指向 const int 的指针的对象,该对象最初指向以前声明的对象。指针本身没有限定类型-它指向限定类型,在程序执行过程中几乎可以更改为指向任何 int。除非使用强制类型转换,否则不能使用 pci 修改它所指向的对象,如下所示:


*(int *)pci = 17;

如果 pci 实际上指向 const 对象,则此代码的行为不确定。

声明


extern int *const cpi;

表明程序中某个位置存在类型为指向 intconst 指针的全局对象的定义。在此情况下,cpi 的值将不会被相应的程序更改,但是可用来修改它指向的对象。请注意,在以上声明中,const 位于 * 之后。以下一对声明产生的效果相同:


typedef int *INT_PTR;
extern const INT_PTR cpi;

这些声明可以合并为以下声明,其中对象的类型声明为指向 const intconst 指针:


const int *const cpci;
  

6.6.3 const 意味着 readonly

根据经验,对于关键字,readonly 优于 const。如果以此方式读取 const,则如下声明:


char *strcpy(char *, const char *);

很容易理解,即第二个参数仅用于读取字符值,而第一个参数覆写它指向的字符。此外,尽管在以上示例中,cpi 的类型是指向 const int 的指针,但您仍可以通过其他某些方法更改它指向的对象的值,除非它确实指向被声明为 const int 类型的对象。

6.6.4 const 用法示例

const 的两种主要用法是将在编译时初始化的大型信息表声明为无变化,以及指定该指针参数不修改它们所指向的对象。

第一种用法潜在允许某个程序的部分数据被同一程序的其他并行调用共享。它可能导致尝试将此不变数据修改为通过某种内存保护故障立即检测,因为该数据驻留在内存的只读部分中。

第二种用法有助于在演示期间生成内存故障之前查找潜在错误。例如,如果将指针传递给无法进行如此修改的字符串,则临时将空字符置入字符串中间的函数会在编译时被检测到

6.6.5 volatile 意味着精确语义

到目前为止,示例全都使用 const,因为它在概念上更简单。但是,volatile 到底意味着什么?对于编译器编写者,它只有一种含义:访问此类对象时不采用代码生成快捷方式。在 ISO C 中,声明具有相应属性及 volatile 限定类型的各个对象是程序员的责任。

6.6.6 volatile 用法示例

volatile 对象的四个常见示例为:

前三个示例是具有特殊行为的对象的所有实例:在程序执行期间的任何点均可修改其值。因此,表面上的死循环:


flag = 1;
while (flag);

实际上有效,只要 flag 具有 volatile 限定类型。某些异步事件将来可能将 flag 设置为零。否则,由于 flag 的值在循环主体中保持不变,编译系统会将以上循环更改为完全忽略 flag 值的真正死循环。

第四个示例涉及调用 setjmp 的函数的局部变量,因此进一步加以讨论。关于 setjmplongjmp 行为的细小字体注释表明对于符合第四种情形的对象的值没有任何保证。对于大多数所期望的行为,有必要让 longjmp 检查调用 setjmp 的函数与调用 longjmp 的函数之间的各个栈帧是否有保存的寄存器值。由于存在异步创建栈帧的可能性,使该作业更加困难。

在将自动对象声明为 volatile 限定类型时,编译系统知道生成的代码必须与程序员编写的代码完全匹配。因此,此类自动对象的最新值始终保存在内存中,而不是仅保存在寄存器中,且调用 longjmp 时保证它是最新的。