关键字 restrict 和 volatile

666 阅读2分钟

参考 zhuanlan.zhihu.com/p/349726808…

C/C++ 关键字之 restrict

在 C 语言中,restrict 关键字用于修饰指针。通过加上 restrict 关键字,编程者可以提示编译器:在该指针的生命周期内,其指向的对象不会被别的指针所引用。

需要注意的是,在 C++ 中,并无明确统一的标准支持 restrict 关键字。但是很多编辑器实现了功能相同的关键字,例如 gcc 和 clang 中的 __restrict 关键字。

看个例子:

int add1(int* a, int* b)
{
    *a = 10;
    *b = 12;
    return *a + *b;
}

在指针 a 和 b 地址不同时,返回 22 没有问题。但是当指针 a 与 b 指向的是同一个 int 对象时,该对象先被赋值 10,后被赋值为 12,因此最终返回值 24。

使用 -O3 优化,add1 对应的汇编代码如下。

0000000000400a10 <_Z4add1PiS_>:
  400a10:   c7 07 0a 00 00 00       movl   $0xa,(%rdi) ; *a = 10
  400a16:   c7 06 0c 00 00 00       movl   $0xc,(%rsi) ; *b = 12
  400a1c:   8b 07                   mov    (%rdi),%eax ; 结果 = *a
  400a1e:   83 c0 0c                add    $0xc,%eax   ; 结果 += 12 
  400a21:   c3                      retq

为了得到 *a 的值访问了一次内存,而不管在何种条件下(a == b 或者 a != b),*b 的值都是 12。因此聪明的编译器将 *a 的值载入 eax 寄存器后,直接加上立即数 12,而无需再访问内存获取 *b 的值。在无法确定指针 a 和 b 是否相同的情况下,编译器只能帮你到这里了。

但是如果加上了 restrict 关键字,情况便大不相同。C/C++ 和经过 -O3 优化的汇编代码如下。通过restrict 关键字,编译器依然确认指针 a 和 b 不可能指向同一个内存地址,因此在求 *a + *b时,无需访问内存,因为 *a 必然等于立即数 10,*b必然等于立即数 12。

int add2(int* __restrict a, int* __restrict b)
{
    *a = 10;
    *b = 12;
    return *a + *b;
}
0000000000400a30 <_Z4add2PiS_>:
  400a30:   c7 07 0a 00 00 00       movl   $0xa,(%rdi) ; *a = 10
  400a36:   b8 16 00 00 00          mov    $0x16,%eax  ; 结果 = 22
  400a3b:   c7 06 0c 00 00 00       movl   $0xc,(%rsi) ; *b = 12
  400a41:   c3                      retq

通过无 restrict 和有 restrict 两种情况下的汇编指令可看到,后者比前者少访问一次内存,且少执行一条指令。

注意使用 restrict 的时候,编程者必须确保不会出现 pointer aliasing,即同一块内存无法通过两个或以上的指针变量名访问。不满足这个条件而强行指定 restrict,将会出现 undefined behavior。

C/C++ 关键字之 volatile

用在变量前,说明这个变量是:易变的、不稳定的。

先看一个示例:

1.PNG

对代码使用优化:

2.PNG

在加了 volatile 之后,使用优化

3.PNG

因此,易变是针对编译器优化来说的,编译器会把它认为值不会改变的变量当常量对待,以此换取大幅度的优化,并缩减汇编指令。而 volatile 就是阻止这种优化。让 cpu 从内存中读、写变量。

在举个例子:

4.PNG

如果在另一个文件或者线程中,改变了 a 的值,那么优化掉整个 while 循环就显得不合理了。