命名空间
变体
操作

restrict 类型限定符 (C99 起)

来自 cppreference.cn
< c‎ | language

C 类型系统 中的每种单独类型都有几个限定版本,分别对应 constvolatile 和(对于指向对象类型的指针)restrict 限定符中的一个、两个或全部三个。此页面描述了 restrict 限定符的效果。

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

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

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

在声明了 restrict 限定指针 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 限定指针访问(请注意,如果别名 restrict 限定指针指向的对象反过来是指针,则这种别名化可能会抑制优化)。

从一个 restrict 限定指针赋值到另一个是未定义行为,除非是从某个外部代码块中的对象的指针赋值到某个内部代码块中的指针(包括在调用带有 restrict 限定指针参数的函数时使用 restrict 限定指针参数),或者从函数返回时(以及在其他情况下,当源指针的代码块结束时)

int* restrict p1 = &a;
int* restrict p2 = &b;
p1 = p2; // undefined behavior

Restrict 限定指针可以自由地赋值给非 restrict 限定指针,只要编译器能够分析代码,优化机会仍然存在

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 限定的,但其元素类型是

(直到 C23)

数组类型及其元素类型始终被视为具有相同的 restrict 限定

(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 限定指针做出的别名断言不被违反。

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

[编辑] 用法模式

restrict 限定指针有几种常见的用法模式

[编辑] 文件作用域

文件作用域 restrict 限定指针必须在程序的整个持续时间内指向单个数组对象。该数组对象可能不会通过 restrict 限定指针及其声明的名称(如果有)或另一个 restrict 限定指针来引用。

文件作用域 restrict 限定指针在提供对动态分配的全局数组的访问时非常有用;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 限定指针最流行的用例是用作函数参数。

在以下示例中,编译器可以推断出没有修改对象的别名,因此可以积极地优化循环。在进入 f 时,restrict 限定指针 a 必须提供对其关联数组的独占访问权。特别是,在 f 中,bc 都可能不指向与 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<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 的 const 性保证了它在函数体中不会变得依赖于 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 限定指针做出限制在其代码块内的别名断言。它允许仅应用于重要代码块(例如紧密循环)的局部断言。它还可以将接受 restrict 限定指针的函数转换为宏

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 限定指针所做的别名断言的作用域是用于访问结构体的标识符的作用域。

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

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)