只是想知道:当我向指针添加restrict时,我告诉编译器指针不是另一个指针的别名.我们假设我有一个类似的函数:
// Constructed example void foo (float* result, const float* a, const float* b, const size_t size) { for (size_t i = 0; i < size; ++i) { result [i] = a [0] * b [i]; } }
如果编译器必须假设result
可能重叠a
,则必须每次重新获取.但是,正如a
标记的那样const
,编译器也可以假设a是固定的,因此一次取回它就可以了.
问题是,在这种情况下,使用restrict的推荐方法是什么?我当然不希望编译器a
每次都重新获取,但我找不到关于如何restrict
在这里工作的好信息.
你的指针是const,告诉任何调用你的函数的人你不会触及通过该变量指向的数据.不幸的是,编译器仍然不知道结果是否是const指针的别名.您始终可以使用非常量指针作为常量指针.例如,许多函数将const char(即字符串)指针作为参数,但是如果您愿意,可以将它传递给非const指针,该函数仅仅是让您承诺它不会使用该特定的指针改变任何东西.
基本上,为了更接近你的问题,你需要为a和b添加限制,以便"保证"编译器使用此函数的人不会将结果作为别名传递给a或b.当然,假设你能够做出这样的承诺.
这里的每个人似乎都很困惑.到目前为止,在任何答案中都没有一个const指针的例子.
该声明const float* a
是不是一个常量指针,它是常量存储.指针仍然是可变的.float *const a
是一个指向可变浮点的const指针.
所以问题应该是,是否有任何意义float *const restrict a
(或者const float *const restrict a
如果您愿意).
是的,你需要限制. Pointer-to-const并不意味着没有任何东西可以改变数据,只是你无法通过指针改变它.
const
主要是一种机制,要求编译器帮助您跟踪您希望功能允许修改的内容. const
并不是对编译器的承诺,函数实际上不会修改数据.
与restrict
使用指向const
可变数据的指针基本上是对其他人的承诺,而不是对编译器的承诺.const
除非您尝试修改编译器放入只读内存中的内容(请参阅下面的static const
变量),否则抛弃所有地方不会导致优化器(AFAIK)出现错误行为.如果编译器在优化时无法看到函数的定义,则必须假设它const
通过该指针抛弃并修改数据(即函数不尊重const
其指针args 的ness).
但编译器确实知道static const int foo = 15;
无法更改,即使将其地址传递给未知函数,也会可靠地内联该值.(这就是为什么static const int foo = 15;
并不比#define foo 15
优化编译器慢.好的编译器会constexpr
尽可能优化它.)
请记住,这restrict
是对编译器的承诺,即您通过该指针访问的内容不会与其他任何内容重叠.如果这不是真的,那么你的功能不一定会达到你的预期.例如,不要打电话foo_restrict(buf, buf, buf)
到原地操作.
根据我的经验(使用gcc和clang),restrict
主要用于存储的指针.连接restrict
源指针也没什么坏处,但是如果你的函数所做的所有存储都是通过restrict
指针,通常你只需要将目标指针放在目标指针上即可获得所有的asm改进.
如果你的循环中有任何函数调用,restrict
在源指针上确实让clang(但不是gcc)避免重新加载.在Godbolt编译器资源管理器上查看这些测试用例,特别是这个:
void value_only(int); // a function the compiler can't inline int arg_pointer_valonly(const int *__restrict__ src) { // the compiler needs to load `*src` to pass it as a function arg value_only(*src); // and then needs it again here to calculate the return value return 5 + *src; // clang: no reload because of __restrict__ }
gcc6.3(针对x86-64 SysV ABI)决定src
在函数调用中将(指针)保留在调用保留寄存器中,并*src
在调用后重新加载.无论是gcc的算法都没有发现这种优化的可能性,或者认为它不值得,或者gcc devs故意没有实现它,因为他们认为它不安全.IDK哪个.但是既然clang做了,我猜它根据C11标准可能是合法的.
clang4.0优化它只加载*src
一次,并将值保存在函数调用的保持调用寄存器中.没有restrict
,它不会这样做,因为被调用的函数可能(作为副作用)*src
通过另一个指针修改.
例如,此函数的调用者可能已传递全局变量的地址.但是*src
除了通过src
指针之外的任何修改都会违反restrict
对编译器的承诺.由于我们没有传递src
给valonly()
,编译器可以假设它不会修改该值.
C语言的GNU方言允许使用__attribute__((pure))
或__attribute__((const))
声明一个函数没有副作用,允许这种优化restrict
,但是在ISO C11(AFAIK)中没有可移植的等价物.当然,允许函数内联(通过将其放在头文件中或使用LTO)也允许这种优化,并且对于小函数(尤其是在内部循环中调用)更好.
编译器通常非常积极地进行标准允许的优化,即使它们对某些程序员感到惊讶并且破坏了一些恰好工作的现有不安全代码.(C是如此可移植,以至于很多东西都是基本标准中未定义的行为;大多数好的实现确实定义了标准留下的许多东西的行为作为UB.)C不是一种在编译器中抛出代码是安全的语言.它做你想要的,而不检查你是否正确地做(没有符号整数溢出等)
如果你看一下x86-64 asm输出来编译你的函数(从问题中),你可以很容易地看到差异.我把它放在Godbolt编译器资源管理器上.
在这种情况下,把restrict
上a
足以让铛葫芦的负载a[0]
,而不是的gcc.
有了float *restrict result
,clang和gcc都会提升负载.
例如
# gcc6.3, for foo with no restrict, or with just const float *restrict a .L5: vmovss xmm0, DWORD PTR [rsi] vmulss xmm0, xmm0, DWORD PTR [rdx+rax*4] vmovss DWORD PTR [rdi+rax*4], xmm0 add rax, 1 cmp rcx, rax jne .L5
与
# gcc 6.3 with float *__restrict__ result # clang is similar with const float *__restrict__ a but not on result. vmovss xmm1, DWORD PTR [rsi] # outside the loop .L11: vmulss xmm0, xmm1, DWORD PTR [rdx+rax*4] vmovss DWORD PTR [rdi+rax*4], xmm0 add rax, 1 cmp rcx, rax jne .L11
总而言之,把__restrict__
所有保证不与其他东西重叠的指针放在一起.
BTW,restrict
只是C中的一个关键字.一些C++编译器支持__restrict__
或__restrict
作为扩展,所以你应该#ifdef
把它放在未知的编译器上.
以来
在C-99标准(ISO/IEC 9899:1999(E))中const * restrict
,有例如第7.8.2.3节中的例子:
#includeintmax_t strtoimax(const char * restrict nptr, char ** restrict endptr, int base); --- snip ---
因此,如果假设标准不提供这样的例子,如果它们是const *
多余的* restrict
,那么它们实际上并不是多余的.