是的,你需要限制。 Pointer-to-const 并不意味着没有任何东西可以改变数据,只是你不能通过那个指针来改变它。
const
主要是一种机制,要求编译器帮助您跟踪您希望允许函数修改哪些内容。 const
不是向编译器承诺函数真的不会修改数据。
与 不同restrict
的是,使用指向const
可变数据的指针基本上是对其他人的承诺,而不是对编译器的承诺。到处丢弃const
不会导致优化器 (AFAIK) 出现错误行为,除非您尝试修改编译器放入只读内存的内容(请参阅下面的static const
变量)。如果编译器在优化时看不到函数的定义,它必须假设它const
通过该指针丢弃和修改数据(即该函数不尊重const
其指针参数的性质)。
但是,编译器确实知道static const int foo = 15;
无法更改,并且即使您将其地址传递给未知函数,它也会可靠地内联该值。(这就是为什么static const int foo = 15;
不比#define foo 15
优化编译器慢的原因。好的编译器会constexpr
尽可能地优化它。)
请记住,这restrict
是对编译器的承诺,即您通过该指针访问的内容不会与其他任何内容重叠。如果这不是真的,那么您的功能不一定会达到您的预期。例如,不要打电话foo_restrict(buf, buf, buf)
就地操作。
以我的经验(使用 gcc 和 clang),restrict
主要对您存储的指针有用。放在源指针上也没有什么坏处,但是如果你的函数所做的所有restrict
存储都是通过指针,通常你可以通过将它放在目标指针上来获得所有可能的 asm 改进。restrict
如果您的循环中有任何函数调用,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 开发人员故意没有实现它,因为他们认为它不安全。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
足以让 clang 提升 的负载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__
所有保证不与其他内容重叠的指针。
顺便说一句,restrict
只是 C 中的一个关键字。一些 C++ 编译器支持__restrict__
或__restrict
作为扩展,所以你应该#ifdef
在未知的编译器上使用它。
自从