我总是不确定,限制关键字在c++中是什么意思?

这是否意味着指向该函数的两个或多个指针不重叠? 还有别的意思吗?


当前回答

由于一些C库的头文件使用关键字,c++语言将不得不对此做一些事情。在最低限度,忽略关键字,所以我们不需要#定义关键字到一个空白宏来抑制关键字。

其他回答

由于一些C库的头文件使用关键字,c++语言将不得不对此做一些事情。在最低限度,忽略关键字,所以我们不需要#定义关键字到一个空白宏来抑制关键字。

c++中没有这样的关键字。c++关键字列表可以在c++语言标准的2.11/1节中找到。restrict是C99版本C语言中的关键字,而不是c++中的关键字。

使用__restrict__,编译器可以进行复杂的优化,因为程序员已经保证__restrict__修饰过的指针指向的数据范围肯定不会相互重叠。

这是通常的情况,所以为了达到高性能的目标,你可以在大多数情况下给你的代码中的指针添加__restrict__装饰器。

正如其他人所说,它在c++ 14中没有任何意义,所以让我们考虑__restrict__ GCC扩展,它与C99的restrict功能相同。

C99

Restrict表示两个指针不能指向重叠的内存区域。最常见的用法是函数参数。

这限制了函数的调用方式,但允许进行更多的编译优化。

如果调用方不遵守限制契约,就会发生未定义的行为。

C99 N1256草案6.7.3/7“类型限定符”说:

限制限定符(像寄存器存储类一样)的预期用途是促进优化,并且从组成符合程序的所有预处理转换单元中删除限定符的所有实例并不会改变其含义(即可观察的行为)。

6.7.3.1“限制的形式化定义”给出了血淋淋的细节。

一种可能的优化

维基百科的例子很有启发性。

它清楚地显示了如何保存一个汇编指令。

没有限制:

void f(int *a, int *b, int *x) {
  *a += *x;
  *b += *x;
}

伪汇编:

load R1 ← *x    ; Load the value of x pointer
load R2 ← *a    ; Load the value of a pointer
add R2 += R1    ; Perform Addition
set R2 → *a     ; Update the value of a pointer
; Similarly for b, note that x is loaded twice,
; because x may point to a (a aliased by x) thus 
; the value of x will change when the value of a
; changes.
load R1 ← *x
load R2 ← *b
add R2 += R1
set R2 → *b

限制:

void fr(int *restrict a, int *restrict b, int *restrict x);

伪汇编:

load R1 ← *x
load R2 ← *a
add R2 += R1
set R2 → *a
; Note that x is not reloaded,
; because the compiler knows it is unchanged
; "load R1 ← *x" is no longer needed.
load R2 ← *b
add R2 += R1
set R2 → *b

GCC真的做到了吗?

g++ 4.8 Linux x86-64:

g++ -g -std=gnu++98 -O0 -c main.cpp
objdump -S main.o

对于-O0,它们是一样的。

o3:

void f(int *a, int *b, int *x) {
    *a += *x;
   0:   8b 02                   mov    (%rdx),%eax
   2:   01 07                   add    %eax,(%rdi)
    *b += *x;
   4:   8b 02                   mov    (%rdx),%eax
   6:   01 06                   add    %eax,(%rsi)  

void fr(int *__restrict__ a, int *__restrict__ b, int *__restrict__ x) {
    *a += *x;
  10:   8b 02                   mov    (%rdx),%eax
  12:   01 07                   add    %eax,(%rdi)
    *b += *x;
  14:   01 06                   add    %eax,(%rsi) 

对于不熟悉的人,调用约定是:

Rdi =第一个参数 Rsi =第二个参数 RDX =第三个参数

GCC的输出甚至比wiki文章更清楚:4条指令vs 3条指令。

数组

到目前为止,我们只节省了一条指令,但是如果指针表示要循环的数组,这是一个常见的用例,那么就可以节省一堆指令,就像supercat和michael提到的那样。

举个例子:

void f(char *restrict p1, char *restrict p2, size_t size) {
     for (size_t i = 0; i < size; i++) {
         p1[i] = 4;
         p2[i] = 9;
     }
 }

由于限制,智能编译器(或人工)可以优化为:

memset(p1, 4, size);
memset(p2, 9, size);

哪个可能更有效,因为它可能是在一个像样的libc实现(如glibc)上进行汇编优化。就性能而言,使用std::memcpy()或std::copy()更好吗?,可能使用SIMD指令。

如果没有限制,这个优化就无法完成,例如考虑:

char p1[4];
char *p2 = &p1[1];
f(p1, p2, 3);

然后for version生成:

p1 == {4, 4, 4, 9}

而memset版本使得:

p1 == {4, 9, 9, 9}

GCC真的做到了吗?

GCC 5.2.1。Linux x86-64 Ubuntu 15.10:

gcc -g -std=c99 -O0 -c main.c
objdump -dr main.o

对于-O0,两者是一样的。

o3:

with restrict: 3f0: 48 85 d2 test %rdx,%rdx 3f3: 74 33 je 428 <fr+0x38> 3f5: 55 push %rbp 3f6: 53 push %rbx 3f7: 48 89 f5 mov %rsi,%rbp 3fa: be 04 00 00 00 mov $0x4,%esi 3ff: 48 89 d3 mov %rdx,%rbx 402: 48 83 ec 08 sub $0x8,%rsp 406: e8 00 00 00 00 callq 40b <fr+0x1b> 407: R_X86_64_PC32 memset-0x4 40b: 48 83 c4 08 add $0x8,%rsp 40f: 48 89 da mov %rbx,%rdx 412: 48 89 ef mov %rbp,%rdi 415: 5b pop %rbx 416: 5d pop %rbp 417: be 09 00 00 00 mov $0x9,%esi 41c: e9 00 00 00 00 jmpq 421 <fr+0x31> 41d: R_X86_64_PC32 memset-0x4 421: 0f 1f 80 00 00 00 00 nopl 0x0(%rax) 428: f3 c3 repz retq Two memset calls as expected. without restrict: no stdlib calls, just a 16 iteration wide loop unrolling which I do not intend to reproduce here :-)

我还没有耐心对它们进行基准测试,但我相信限制版本会更快。

严格混叠规则

restrict关键字只影响兼容类型的指针(例如两个int*),因为严格的混叠规则规定,默认情况下,混叠不兼容类型是未定义的行为,因此编译器可以假设它不会发生并进行优化。

看:什么是严格的混叠规则?

这对推荐信有用吗?

根据GCC文档,它的语法是:https://gcc.gnu.org/onlinedocs/gcc-5.1.0/gcc/Restricted-Pointers.html:

int &__restrict__ rref

甚至还有一个成员函数this的版本:

void T::fn () __restrict__

没什么。它被添加到C99标准中。