命名空间
变体
操作

restrict 类型限定符 (自 C99 起)

来自 cppreference.cn
< c‎ | 语言

C 类型系统 中的每个独立类型都有该类型的几个“限定”版本,对应于 constvolatile 以及(对于指向对象类型的指针)restrict 限定符中的一个、两个或全部三个。本页描述 restrict 限定符的效果。

只有指向对象类型的指针或其(可能是多维)数组(自 C23 起)可以被 restrict-qualified;特别是,以下是错误的

  • int restrict *p
  • float (* restrict f9)(void)

Restrict 语义仅适用于左值表达式;例如,转换为 restrict-qualified 指针或返回 restrict-qualified 指针的函数调用不是左值,并且限定符没有效果。

在声明受限指针 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
}

如果对象从未被修改,它可以通过不同的 restrict-qualified 指针进行别名访问(请注意,如果别名 restrict-qualified 指针指向的对象本身是指针,则此别名可能会抑制优化)。

从一个受限指针赋值给另一个受限指针是未定义行为,除非是从外层块中指向对象的指针赋值给内层块中的指针(包括在调用具有受限指针参数的函数时使用受限指针实参)或者从函数返回时(以及在 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++
}

如果数组类型使用 restrict 类型限定符(通过使用 typedef)声明,则数组类型不是 restrict-qualified,但其元素类型是

(直至 C23)

数组类型及其元素类型始终被认为是具有相同的 restrict-qualified

(自 C23 起)
typedef int *array_t[10];
 
restrict array_t a; // the type of a is int *restrict[10]
// Notes: clang and icc reject this on the grounds that array_t is not a pointer type
 
void *unqual_ptr = &a; // OK until C23; error since C23
// Notes: clang applies the rule in C++/C23 even in C89-C17 modes

在函数声明中,关键字 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 限定符的预期用途(如 register 存储类)是促进优化,并且从构成符合程序的预处理翻译单元中删除所有限定符实例不会改变其含义(即,可观察行为)。

编译器可以自由地忽略 restrict 用法的任何或所有别名含义。

为避免未定义行为,程序员必须确保 restrict-qualified 指针所做的别名断言不会被违反。

许多编译器作为语言扩展提供了 restrict 的反义词:一个属性,指示即使指针类型不同,它们也可能别名:may_alias (gcc),

[编辑] 使用模式

有几种常见的 restrict-qualified 指针使用模式

[编辑] 文件作用域

文件作用域 restrict-qualified 指针在程序运行期间必须指向单个数组对象。该数组对象不能同时通过受限指针和通过其声明名称(如果有)或另一个受限指针引用。

文件作用域受限指针可用于提供对动态分配的全局数组的访问;restrict 语义使得通过此指针的引用能够像通过其声明名称引用静态数组一样有效地进行优化

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

[编辑] 函数参数

restrict-qualified 指针最流行的用例是作为函数参数使用。

在以下示例中,编译器可以推断没有修改对象的别名,从而积极优化循环。进入 f 时,受限指针 a 必须提供对其关联数组的独占访问。特别是,在 f 中,bc 都不能指向与 a 关联的数组,因为两者都没有被赋值基于 a 的指针值。对于 b,这从其声明中的 const-qualifier 中显而易见,但对于 c,需要检查 f 的主体

float x[100];
float *c;
 
void f(int n, float * restrict a, float * const b)
{
    int i;
    for ( i=0; i<n; 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 关联的数组。另请注意,出于这些目的,“数组”与特定指针关联仅表示通过该指针实际引用的数组对象的一部分。

请注意,在上面的示例中,编译器可以推断 a 和 b 不会别名,因为 b 的 constness 保证它不能在函数体中依赖于 a。同样,程序员可以编写 void f(int n, float * a, float const * restrict b),在这种情况下,编译器可以推断通过 b 引用的对象不能被修改,因此不能同时使用 b 和 a 引用修改过的对象。如果程序员编写 void f(int n, float * restrict a, float * b),编译器将无法在不检查函数体的情况下推断 a 和 b 不会别名。

通常,最好在函数原型中明确地用 restrict 注释所有非别名指针。

[编辑] 块作用域

块作用域 restrict-qualified 指针做出一个别名断言,该断言仅限于其块。它允许仅适用于重要块(如紧密循环)的局部断言。它还使得将接受 restrict-qualified 指针的函数转换为宏成为可能

float x[100];
float *c;
 
#define f3(N, A, B)                                    \
do                                                     \
{   int n = (N);                                       \
    float * restrict a = (A);                          \
    float * const    b = (B);                          \
    int i;                                             \
    for ( i=0; i<n; i++ )                              \
        a[i] = b[i] + c[i];                            \
} while(0)

[编辑] 结构体成员

作为结构体成员的 restrict-qualified 指针所做的别名断言的作用域是用于访问结构体的标识符的作用域。

即使结构体在文件作用域声明,当用于访问结构体的标识符具有块作用域时,结构体中的别名断言也具有块作用域;别名断言仅在块执行或函数调用中生效,具体取决于此结构体类型的对象是如何创建的

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 ff.
   // ...
}

[编辑] 关键字

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

[编辑] 参考

  • C23 标准 (ISO/IEC 9899:2024)
  • 6.7.3.1 restrict 的形式定义 (p: TBD)
  • C17 标准 (ISO/IEC 9899:2018)
  • 6.7.3.1 restrict 的形式定义 (p: 89-90)
  • C11 标准 (ISO/IEC 9899:2011)
  • 6.7.3.1 restrict 的形式定义 (p: 123-125)
  • C99 标准 (ISO/IEC 9899:1999)
  • 6.7.3.1 restrict 的形式定义 (p: 110-112)