C类型系统中的每个单独类型都具有该类型的多个限定版本,对应于const,volatile中的一个,两个或全部三个,并且对于指向对象类型的指针,限制限定符。本页介绍的影响限制预选赛。
仅指向对象类型可以是限制合格(特别地,int restrict *p和float (* restrict f9)(void)是错误)。
限制语义仅适用于左值表达式; 例如,转换为限制限定指针或返回限制限定指针的函数调用不是左值,并且限定符不起作用。
在P声明限制指针的块的每次执行过程中(通常每个函数体的每个执行P都是一个函数参数),如果通过P(直接或间接)方式访问的某个对象以任何方式被修改,则所有在该块中访问该对象(读取和写入)必须通过P(直接或间接)发生,否则行为未定义:
void f(int n, int * restrict p, int * restrict q){ while(n-- > 0) *p++ = *q++; // none of the objects modified through *p is the same // as any of the objects read through *q // compiler free to optimize, vectorize, page map, etc.}void g(void){
extern int d[100]; f(50, d + 50, d); // OK f(50, d + 1, d); // Undefined behavior: d[1] is accessed through both p and q in f}
如果对象从不修改,则可能会被别名和通过不同的限制限定指针访问(请注意,如果由别名限制限定指针指向的对象又是指针,则此别名可能会禁止优化)。
从一个受限指针指向另一个受限指针是未定义的行为,除非将指向某个外部块中的对象的指针指定给某个内部块中的指针(包括在调用具有受限指针参数的函数时使用受限指针参数),或者何时从一个函数返回(否则当from-pointer结束时):
int* restrict p1 = &a;int* restrict p2 = &b;p1 = p2; // undefined behavior
受限制的指针可以自由分配给无限制的指针,只要编译器能够分析代码,优化机会就会保持原状:
void f(int n, float * restrict r, float * restrict s) {
float * p = r, * q = s; // OK while(n-- > 0) *p++ = *q++; // almost certainly optimized just like *r++ = *s++}
如果使用限制类型限定符(通过使用typedef)声明数组类型,则数组类型不受限制,但其元素类型为:
typedef int *array_t[10];restrict array_t a; // the type of a is int *restrict[10]
在函数声明中,关键字restrict可能出现在用于声明函数参数的数组类型的方括号内。它限定了数组类型转换的指针类型:
void f(int m, int n, float a[restrict m][n], float b[restrict m][n]);void g12(int n, float (*p)[n]) { f(10, n, p, p+10); // OK f(20, n, p, p+10); // possibly undefined behavior (depending on what f does)}
注意
限制限定符(如注册存储类)的预期用途是促进优化,并且从组成合格程序的所有预处理翻译单元中删除所有限定符的实例不会改变其含义(即可观察到的行为)。
编译器可以自由地忽略使用的任何或所有别名含义restrict。
为了避免未定义的行为,程序员必须确保不受违反限制限制指针的别名声明。
作为语言扩展,许多编译器提供了与之相反restrict的属性:一个属性,指示即使类型不同,指针也可以是别名:may_alias(gcc),
限制限定指针有几种常见的使用模式:
文件范围
文件范围限制限定指针必须在程序期间指向单个数组对象。该数组对象不能通过受限制的指针和声明的名称(如果它有一个)或另一个受限制的指针来引用。
文件范围受限指针在提供对动态分配的全局数组的访问时很有用; 限制语义可以通过这个指针来优化引用,就像通过声明名称引用静态数组一样有效:
float * restrict a, * restrict b;float c[100];
int init(int n) {
float * t = malloc(2*n*sizeof(float));
a = t; // a refers to 1st half
b = t + n; // b refers to 2nd half}// compiler can deduce from the restrict qualifiers that// there is no potential aliasing among the names a, b, and c
函数参数
限制限定指针最常用的用例是用作函数参数。
在以下示例中,编译器可能会推断出没有修改对象的别名,因此积极优化循环。一旦进入f,受限指针a必须提供对其相关阵列的独占访问权限。特别是,在f中,b和c都不会指向与a相关的数组,因为它们都不会被指定基于a的指针值。对于b,从声明中的const限定词中可以明显看出,但对于c,需要检查f的主体:
float x[100];float *c;void f(int n, float * restrict a, float * const b) {
int i; for ( i=0; i
a[i] = b[i] + c[i];}void g3(void) {
float d[100], e[100];
c = x; f(100, d, e); // OK f( 50, d, d+50); // OK f( 99, d+1, d); // undefined behavior
c = d; f( 99, d+1, e); // undefined behavior f( 99, e, d+1); // OK}
请注意,允许c指向与b关联的数组。还要注意,出于这些目的,与特定指针关联的“数组”只意味着通过该指针实际引用的数组对象的那部分。
块范围
块范围限制限定指针使得限制为其块的别名断言成为可能。它允许局部断言只适用于重要的块,比如紧密的循环。它还可以将一个将限制合格指针的函数转换为一个宏:
float x[100];float *c;#define f3(N, A, B) \{ int n = (N); \
float * restrict a = (A); \
float * const b = (B); \
int i; \ for ( i=0; i
a[i] = b[i] + c[i]; \}
结构成员
由作为结构成员的限制限定指针产生的别名声明的范围是用于访问该结构的标识符的范围。
即使结构在文件范围内声明,当用于访问结构的标识符具有块范围时,结构中的别名声明也具有块范围; 别名声明仅在块执行或函数调用中生效,具体取决于如何创建此结构类型的对象:
struct t { // Restricted pointers assert that
int n; // members point to disjoint storage.
float * restrict p;
float * restrict q;}; void ff(struct t r, struct t s) {
struct t u; // r,s,u have block scope // r.p, r.q, s.p, s.q, u.p, u.q should all point to // disjoint storage during each execution of f. // ...}
关键词
restrict.
例
代码生成示例; 用-S(gcc,clang等)或/ FA(visual studio)编译。
int foo(int *a, int *b){ *a = 5; *b = 6; return *a + *b;}
int rfoo(int *restrict a, int *restrict b){ *a = 5; *b = 6; return *a + *b;}
可能的输出:
# generated code on 64bit Intel platform:foo:
movl $5, (%rdi) # store 5 in *a
movl $6, (%rsi) # store 6 in *b movl (%rdi), %eax # read back from *a in case previous store modified it
addl $6, %eax # add 6 to the value read from *a
ret
rfoo:
movl $11, %eax # the result is 11, a compile-time constant
movl $5, (%rdi) # store 5 in *a
movl $6, (%rsi) # store 6 in *b
ret