说明

  • restrict是c99标准新增的一个关键字,是一种类型限定符(Type Qualifiers)。

作用

  • 程序员通过该关键字告诉编译器,我保证代码中restrict的指针独占其指向的内存,所有访问/修改其内存的操作都是基于该指针的,没有其他直接或间接的方式(其它变量或指针),以便编译器进行更好的代码优化和生成更高效的汇编代码。
  • restrict的优化效果是不一定的,只是帮助编译器优化特定场景,出现完全没有优化也是可能的,如果不使用restrict,在特定场景下编译器是不会进行优化。
  • restrict指针的独占作用是由程序员代码编写保证的,不是由编译器保证的,并不准确和稳妥。

编译优化

  • restrict的出现是为了解决指针的一部分缺陷(功能强大,限制方法较少,使用过于自由),例如:两个指针指向同一块内存或者指向的内存存在重叠,编译器无法获知这些情况,为了保证程序运行的正确性,编译结果可能会多次读取同一地址内存,如下:
int foo(int *a, int *b) 
{
    *a = 5;
    *b = 6;
    return *a + *b;
}
* 如果a和b指向同一块内存,运行结果是12,如果是不同内存,运行结果是11,
编译器为了保证运行结果的正确性,就需要每次都从内存中读取,汇编代码如下:
foo:
    movl    $5, (%rdi)    # 存储 5 至 *a
    movl    $6, (%rsi)    # 存储 6 至 *b
    movl    (%rdi), %eax  # 重新读取 *a (因为有可能被上一行指令造成改变)
    addl    $6, %eax      # 加上 6
    ret
  • 如果我们确保两个指针不指向同一数据,就可以用 restrict修饰指针类型,如下:
int foo(int *restrict a, int *restrict b) 
{
    *a = 5;
    *b = 6;
    return *a + *b;
}
* 编译器就可以根据这个信息,做出优化:
rfoo:
    movl      $11, %eax   # 在编译期已计算出 11
    movl      $5, (%rdi)  # 存储 5 至 *a
    movl      $6, (%rsi)  # 存储 6 至 *b
    ret

常见示例

  • C标准库
void *memcpy( void * restrict dest ,const void * restrict src,size_t n) 
void memmove(void *dest,const void * src,size_t n)
* memcpy是内存复制函数,由于两个参数都加了restrict限定,所以两块区域不能重叠,memmove 则可以重叠。

使用

  • 关键字restrict只用于限定指针。

使用场景

  1. 非常需要性能。
  • 使用restrict关键字需要程序员来保证,指针对内存的独占性,有些情况下,程序员也不能保证,如果无法保证,就会出现问题,并且问题不好确认,所以可能为了性能增加了代码风险,得不偿失。
  1. 明确知道某两个指针在业务逻辑上不会、也不能重叠

示例

  1. 函数参数限制
int xxx(int *restrict a, int *restrict b) 
{
    ....
}
  1. 变量限制
int * restrict xxx = (int *)malloc(10 * sizeof(int));

编译

gcc --std=c99 -O1 xxx.c
  • 需要加上–std=c99 不然编译器识别不了restrict,必须要加上优化选项,否则编译器会忽略 restrict。