restrict关键字在C ++中的含义是什么?
我总是不确定,C ++中的restrict关键字是什么意思?
这是否意味着给函数的两个或多个指针不重叠? 这是什么意思?
Christer Ericson在他的论文“内存优化”中说,虽然restrict
并不是C ++标准的一部分,但它被许多编译器支持,而且他建议在可用时使用它:
限制关键字
! 1999年新的ANSI / ISO C标准
! 目前还不在C ++标准中,但被许多C ++编译器支持
! 只有一个提示,所以可能什么也不做,仍然是一致的
限制合格的指针(或引用)...
! ...基本上是对编译器的一个承诺,即对于指针的作用域,指针的目标只能通过该指针(以及从其中复制的指针)访问。
在支持它的C ++编译器中,它应该与C中的相同。
查看此SO帖子以获取详细信息:C99'restrict'关键字的实际使用情况?
花半个小时浏览Ericson的论文,这很有趣,值得花时间。
编辑
我还发现IBM的AIX C / C ++编译器支持__restrict__
关键字。
g ++似乎也支持这一点,因为下面的程序在g ++上干净地编译:
#include <stdio.h>
int foo(int * __restrict__ a, int * __restrict__ b) {
return *a + *b;
}
int main(void) {
int a = 1, b = 1, c;
c = foo(&a, &b);
printf("c == %dn", c);
return 0;
}
我还发现了一篇关于使用restrict
的好文章:
解密限制关键字
EDIT2
我碰到了一篇专门讨论在C ++程序中使用restrict的文章:
Load-hit-stores和__restrict关键字
此外,Microsoft Visual C ++还支持__restrict
关键字。
正如其他人所说,如果没有C ++ 14那样的意思,那么让我们考虑一下__restrict__
GCC扩展,它和C99的restrict
。
C99
restrict
指出两个指针不能指向重叠的内存区域。 最常用的用法是函数参数。
这限制了函数如何被调用,但是允许更多的编译优化。
如果调用者不遵守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 a may be equal to x.
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
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
=第三个参数 海湾合作委员会的输出比维基文章更清晰:4条指令与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;
}
}
由于restrict
,一个智能编译器(或人类)可以将其优化为:
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
版本使:
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
:
与限制:
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
按预期拨打两个memset
电话。
没有限制:没有stdlib调用,只是一个16迭代宽循环展开,我不打算在这里重现:-)
我没有耐心来衡量它们,但我相信限制版本会更快。
严格的锯齿规则
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标准中。
链接地址: http://www.djcxy.com/p/6179.html