ISO/IEC 9899:2011 条款6.7.3——类型限定符
6.7.3 类型限定符
语法
1、type-qualifier:
const
restrict
volatile
_Atomic
约束
2、除了指针类型(其被引用的类型是一个对象类型)之外的类型,不应该被restrict限定。
3、被_Atomic修饰的类型不应该是一个数组类型或一个函数类型。
语义
4、与限定类型相关联的属性仅对作为左值的表达式有意义。[注:实现可以将一个非volatile的一个const对象放置到一个只读存储区域。此外,实现不需要为这么一个对象分配存储空间,如果其地址永远不被使用。]
5、如果同一个限定符在同一个specifier-qualifier-list中出现了多次,要么是直接出现,要么是通过多个typedef出现,那么行为就好比它仅出现一次。如果在一个specifier-qualifier-list中,其它限定符与_Atomic限定符一同出现,那么结果类型是如此限定的原子类型。
6、如果企图通过使用不具有const限定的类型的一个左值来修改一个用const限定类型定义的对象,那么行为是未定义的。如果企图通过使用一个不具有volatile限定类型的一个左值来引用一个用volatile限定类型定义的一个对象,那么行为是未定义的。[注:这应用于这么些对象,其行为就好比它们用限定类型定义,即便它们实际上没有在程序中定义为对象(比如在一个存储器映射的输入/输出地址上的一个对象)。]
7、一个具有volatile限定类型的对象可以用对实现未知的方法进行修改,或具有其它未知的副作用。因此,任一引用这么一个对象的表达式应该根据在5.1.2.3中所描述的抽象机的规则来严格计算。此外,在每个最后存储在对象中的值的顺序点应该遵循由该抽象机所规定的条约,除了作为由先前所提到的未知因素所修改的情况。[注:如何构成对一个具有volatile限定类型的对象的访问是由实现定义的。]
8、通过一个restrict限定的指针来访问的一个对象具有一个特殊的与该指针的关联性。这个关联定义在下面的6.7.3.1,要求所有对那个对象的访问,直接或间接使用那个特定指针的值。[注:比如,将由malloc所返回的一个值赋给一单个指针的语句,建立了在所分配的对象与该指针之间的关联性。]对restrict限定符(像register存储类一样)的使用目的在于提升优化,使得从所有组成一个遵循标准的程序的预处理翻译单元中删除该限定符的所有实例,而不改变其意义(也就是说,这是可观察到的行为)。
9、如果数组类型的说明包含了任一类型限定符,那么元素类型就这么被限定,而不是数组类型。如果函数类型的说明包含了任一限定符,那么行为是未定义的。[注:这两个都可能通过使用typedef而发生。]
10、对于两个相兼容的限定类型,两者都应该具有一个兼容类型的完全相同的限定版本;在一个说明符列表内的类型限定符的次序或限定符的次序不影响指定的类型。
11、例1 一个声明的对象
extern const volatile int real_time_clock;
可以被硬件修改,但不能赋值、递增、或递减。
12、以下声明和表达式描述了当类型限定符修改一个聚合类型时的行为:
const struct s { int mem; } cs = { 1 }; struct s acs; // 对象acs是可修改的 typedef int A[2][3]; const A a = {{4, 5, 6}, {7, 8, 9}}; // const int的数组的数组 int *pi; const int *pci; acs = cs; // 有效 cs = ncs; // 违背了用于=的可修改左值的约束 pi = &ncs.mem; // 有效 pi = &cs.mem; // 违背了用于=的类型约束 pci = &cs.mem; // 有效 pi = a[0]; // 无效:a[0]具有类型“const int *”
13、例3 声明
_Atomic volatile int *p;
指定了p具有类型“指向volatile atomic int的指针”,一个指向volatile限定的原子类型的指针。
6.7.3.1 对restrict的正式定义
1、设D是一个普通标识符的声明,它提供了一种将一个对象P指派为一个restrict限定的指向类型T的指针的方式。[译者注:D表示为T * restrict P;]
2、如果D出现在一个语句块内,且不具有extern存储类型,那么将B设为该语句块。如果D出现在一个函数定义的形参声明列表中,那么将B设为相关联的语句块。否则,将B设为main的语句块(或在一个独立式环境中,在程序启动时所调用的函数)。[译者注:
void foo(void) { if(1 > 0) // B语句块1 { T* restrict P; } } void foo2(T* restrict P) // B语句块2 { } int main(void) // B语句块3 { }
]
3、一个指针表达式E被称为基于对象P,如果(在B的执行中的某个顺序点处,B的执行在对E的计算之前)对指向一个数组对象的一个拷贝的P修改为P之前所指向的对象,将改变E的值。[注:换句话说,E依赖于P自己的值,而不是通过P间接引用的一个对象的值。比如,如果标识符p具有类型(int **restrict),那么指针表达式p与p+1都基于由p所指派的restrict限定的指针对象,但*p和p[1]就不是如此。]
4、在对B的每次执行期间,设L为任一左值,且让&L基于P。如果L用于访问它所指派的对象X的值,且X也被修改(通过任何手段),那么要应用以下要求:T不应该用const限定。用于访问X的值的每个其它左值也应该让其地址基于P。出于此子条款的目的,每个修改X的访问应该被认作为也对P进行修改。如果赋给P的值是一个指针表达式E的值,该指针基于另一个restrict限定的指针对象P2,与语句块B2相关联,那么B2的执行应该在B的执行之前开始,要么B2的执行应该在赋值之前结束。如果这些要求没被满足,那么行为是未定义的。[译者注:比如:
int main(void) { // B语句块 T X; T *L = &X; T ** restrict P = &L; // 这样,对于需要访问X的每个其它左值,也应该将其地址基于P,即 T *O = L; // O为另一个要访问X的指针变量 *O = 100; // 通过指针变量O对X进行修改 // 赋给P的值是一个表达式E的值,E含有P2 P = 1 > 0? (++X, &L /** B2语句块 */) : NULL; // 语句块B2在赋值之前结束 }
]
5、这里,对B的一次执行是对程序执行的一部分,它相应于一个标量类型以及与B相关联的自动存储周期对象的生命周期。
6、一个翻译器可以自由地忽略对任一或所有restrict使用的连带影响。
7、例1 在文件域声明了以下代码:
int * restrict a; int * restrict b; extern int c[];
断言了,如果一个对象通过a、b或c其中之一进行访问,并且该对象在程序中的任一地方被修改,那么它永远都不会使用其它两个进行访问。
8、例2 在以下例子中的函数形参声明
void f(int n, int * restrict p, int * restrict q) { while (n-- > 0) *p++ = *q++; }
断言了,在函数的每个执行期间,如果一个对象通过指针形参其中之一进行访问,那么它就不会通过另一个进行访问。
9、从restrict的获益是,它们允许翻译器对函数f做有效依赖分析,而不检查程序中对f的任一调用。成本是,程序员必须对所有这些调用进行检查,以确保它们不会给出未定义行为。比如,下面在g中对f的第二个调用具有未定义行为,因为d[1]到d[49]的每一个元素既通过p又通过q进行访问。
void g(void) { // 译者注:f函数是上述代码片段中所定义的一个拷贝函数 extern int d[100]; f(50, d + 50, d); // 有效 f(50, d + 1, d); // 未定义行为 }
10、例3 以下函数形参声明
void h(int n, int * restrict p, int * restrict q, int * restrict r) { int i; for(i = 0; i < n; i++) p[i] = q[i] + r[i]; }
描述了一个未被修改的对象如何能通过两个用restrict限定的指针来混叠。特别地,如果a与b是两个不相交的数组,那么对形式h(100, a, b, b)的调用具有已定义的行为,因为数组b在函数h中没被修改。
11、例4 在restrict限定的指针之间赋值的限制规则,并不区分一个函数调用与一个等价的嵌套语句块之间的差异。但是有一个例外,只有在嵌套语句块中,restrict限定的指针之间的“外部到内部”赋值,才有已定义的行为。
{ int * restrict p1; int * restrict q1; p1 = q1; // 未定义行为 { int * restrict p2 = p1; // 有效 int * restrict q2 = q1; // 有效 p1 = q2; // 未定义行为[译者注:内部到外部是无效的] p2 = q2; // 未定义行为 } }
12、还有一个例外是,允许一个由restrict限定的指针的值被带出它所在的语句块(或者,更精确地说,是用于指派它的普通标识符),当那个语句块结束执行时来声明该标识符。比如,在下面的例子中,允许new_vector返回一个vector。
typedef struct { int n; float * restrict v; } vector; vector new_vector(int n) { vector t; t.n = n; t.v = malloc(n * sizeof(float)); return t; }