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
用在变量前,说明这个变量是:易变的、不稳定的。
先看一个示例:
对代码使用优化:
在加了 volatile 之后,使用优化
因此,易变是针对编译器优化来说的,编译器会把它认为值不会改变的变量当常量对待,以此换取大幅度的优化,并缩减汇编指令。而 volatile 就是阻止这种优化。让 cpu 从内存中读、写变量。
在举个例子:
如果在另一个文件或者线程中,改变了 a 的值,那么优化掉整个 while 循环就显得不合理了。