如果我得到了restrict
正确的C99 关键字,那么用它来限定一个指针是一个承诺,它引用的数据不会在编译器的后面通过别名修改.
相比之下,我理解const
限定符的方式是编译器强制执行的文档,即在人类编写代码的背后不会修改给定对象.编译器可能会得到一个提示作为副作用,但作为程序员,我并不在乎.
以类似的方式,将restrict
函数原型中的限定符视为要求用户在调用期间确保独占访问("避免别名"或可能更强的东西)是否合适?它应该用作"文件"吗?
此外,是否有一些事情可以理解,restrict
它指向一个指针而不是它指向的数据(如同const
)?
编辑:我原本认为这restrict
可能会影响线程代码,但这似乎是错误的,所以我从问题中删除对线程的引用,以避免混淆读者.
Chris Dodd对关键字有正确的描述.在某些平台中,由于性能原因,它可能非常重要,因为它让编译器知道一旦它通过该指针将数据加载到寄存器上,就不需要再次这样做了.如果没有这种保证,编译器必须在每次写入任何其他可能别名指针时通过指针重新加载数据,这可能导致严重的管道停顿,称为加载命中存储.
const
并且restrict
是不同的概念,并不是const
暗示的情况restrict
.所有人const
都说你不会在该函数范围内写出该指针.甲const
指针仍可被混叠.例如考虑:
int foo( const int *a, int * b ) { *b *= 2; return *a + *b; // induces LHS: *a must be read back immediately // after write has cleared the store queue }
虽然你不能直接写入a
这个函数,但你可以完全合法地调用foo:
int x = 3; foo( &x, &x ); // returns 12
restrict
是一个不同的保证:a != b
在所有电话中的承诺foo()
.
我已经详细介绍了restrict
关键字及其性能影响,Mike Acton也是如此.虽然我们讨论了特定的有序PowerPC,但x86上也存在load-hit-store问题,但是x86的无序执行使得在配置文件中更难隔离该停顿.
并且只是为了强调:如果您完全关心性能,这不是一个神秘或不成熟的优化.restrict
如果使用得当,可以带来非常显着的加速.
关于restrict关键字的最好的"直觉"是它(由程序员对编译器)的保证,对于指针的生命周期,通过该指针访问的内存只能通过该指针而不是通过另一个指针访问或参考或全球地址.因此,重要的是它在指针上作为指针和内存的属性,将两者绑在一起直到指针超出范围.
你知道的大部分都是错的!
常量并不能保证东西不会编译器的背后改变.它只是阻止你写到那个地方.其他东西可能仍然能够写入该位置,因此编译器不能认为它是常量.
正如其他人所说,限制限定符是关于别名.实际上,在第一轮C标准化期间,有一个关于"noalias"关键字的提议.不幸的是,这个提案写得相当糟糕 - 这促使丹尼斯·里奇在这个过程中唯一一次参与,当时他写了一封信,上面写着"诺娜必须去的地方.这不谈判. "
毋庸置疑,'noalias'没有成为C的一部分.当再次尝试的时候,提案写得更好,限制被包含在标准中 - 尽管noalias可能是一个更有意义的名字对于它,这个名字是如此污染,我怀疑任何人甚至考虑尝试使用它.
在任何情况下,限制的主要目的是告诉编译器不会有此项的别名.其中一个原因是允许暂时将事物存储在寄存器中.例如,考虑如下:
void f(int *a, int *b, int *c) { for (int i=0; i<*a; i++) *b += c[i]; }
编译器确实希望将i放入寄存器,并将*a加载到寄存器中,因此当决定是否执行循环的另一次迭代时,它只是将这些寄存器中的值相互比较.不幸的是,它无法做到这一点 - 如果使用此函数的人完全疯了,并且用== b调用它,每次它在循环内写入*b时,新值也是*a的值- 所以它必须在循环的每次迭代中从内存中读取*a ,以防无论谁调用它都是完全疯了.使用restrict告诉编译器它可以生成代码,假设a和b将始终是不同的,因此写入*a将永远不会改变*b(反之亦然).
你的理解基本上是正确的.的restrict
限定符简单地指出,通过如此限定指针访问的数据是仅由精确指针访问.它适用于读取和写入.
编译器不关心并发线程,它不会以任何不同的方式生成代码,您可以根据自己的喜好破坏自己的数据.但它确实需要知道什么指针操作可能会改变全局内存.
Restrict
还带有一个API警告,告诉人类给定的函数是在假设非混淆参数的情况下实现的.
就编译器而言,用户不需要锁定.它只是想通过编译器应该生成的代码来确保它正确地读取应该被破坏的数据,以防没有限定符.添加可以解决这个问题.restrict
restrict
最后,请注意,编译器可能已经在更高的优化级别基于数据类型分析可能的别名,因此restrict
对于具有指向相同类型数据的多个指针的函数来说非常重要.你可以从这个主题中吸取教训,并确保你做的任何故意别名是通过一个union
.
我们可以看到restrict
实际行动:
void move(int *a, int *b) { void move(int *__restrict a, int *__restrict b) { a[0] = b[0]; a[0] = b[0]; a[1] = b[0]; a[1] = b[0]; } } movl (%edx), %eax movl (%edx), %edx movl %eax, (%ecx) movl %edx, (%eax) movl (%edx), %eax movl %edx, 4(%eax) movl %eax, 4(%ecx)
在右列中,restrict
编译器不需要b[0]
从内存重新读取.它能够读取b[0]
并保存在寄存器中%edx
,然后将寄存器两次存储到存储器中.在左栏中,它不知道商店是否a
可能已更改b
.