命名空间
变体
操作

restrict 类型限定符 (自 C99 起)

来自 cppreference.com
< c‎ | 语言

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 指针参数的函数时使用 restrict 指针参数)或从函数返回时(以及当 from-pointer 的块结束时)

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

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 关联的数组。还要注意,为此,与特定指针关联的“数组”仅指实际上通过该指针引用的数组对象的那一部分。

请注意,在上面的示例中,编译器可以推断 ab 不别名,因为 b 的 const 性保证它不能在函数体内依赖 a。等效地,程序员可以编写 void f(int n, float * a, float const * restrict b),在这种情况下,编译器可以推断通过 b 引用的对象不能被修改,因此任何修改过的对象都不能使用 ba 进行引用。如果程序员要编写 void f(int n, float * restrict a, float * b),则编译器将无法在不检查函数主体的情况下推断 ab 的非别名。

一般来说,最好在函数原型中明确地用 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)