如果指针已经标记为const,是否限制C中的帮助?

只是想知道:当我向指针添加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的推荐方法是什么? 我当然不希望编译器每次都重新获取,但我找不到有关如何在这里使用restrict好信息。

你的指针是const,告诉任何调用你的函数的人你不会触及通过该变量指向的数据。 不幸的是,编译器仍然不知道结果是否是const指针的别名。 您始终可以使用非常量指针作为常量指针。 例如,许多函数将const char(即字符串)指针作为参数,但是如果您愿意,您可以将它传递给非const指针,该函数只是让您承诺它不会使用该特定的指针改变任何东西。

基本上,为了更接近你的问题,你需要为a和b添加限制,以便“保证”编译器使用此函数的人不会将结果作为别名传递给a或b。 当然,假设你能够做出这样的承诺。

这里的每个人似乎都很困惑。 到目前为止,在任何答案中都没有一个const指针的例子。

声明const float* a 不是 const指针,它是const存储。 指针仍然是可变的。 float *const a是一个指向可变浮点的const指针。

所以问题应该是, float *const restrict a是否有任何一点float *const restrict a (如果你愿意, const float *const restrict a )。

C-99标准(ISO / IEC 9899:1999(E))中const * restrict例子,例如,在7.8.2.3节中:

strtoimax和strtoumaxfunction

概要

 #include  intmax_t strtoimax(const char * restrict nptr, char ** restrict endptr, int base); --- snip --- 

因此,如果假设标准不能提供这样的例子,如果const *对于* restrict是多余的,那么它们确实不是多余的。

是的,你需要限制。 Pointer-to-const并不意味着没有任何东西可以改变数据,只是你不能通过指针改变它

const主要是一种机制,要求编译器帮助您跟踪您希望允许函数修改的内容。 const不是编译器的承诺,函数实际上不会修改数据

restrict不同,将指针指向const到可变数据基本上是对其他人的承诺,而不是对编译器的承诺。 除非你试图修改编译器放入只读内存的东西(参见下面关于static const变量的内容),否则抛弃const到处都不会导致优化器(AFAIK)的错误行为。 如果编译器在优化时看不到函数的定义,则必须假设它抛弃const并通过该指针修改数据(即函数不遵循其指针args的const )。

编译器确实知道static const int foo = 15; 但是,即使您将其地址传递给未知函数,也无法更改并且可靠地内联该值。 (这就是为什么static const int foo = 15;对于优化编译器来说,并不比#define foo 15慢。好的编译器会尽可能地像constexpr一样优化它。)


请记住, restrict是对编译器的承诺,即您通过该指针访问的内容不会与其他任何内容重叠 。 如果这不是真的,那么你的function不一定会达到预期的效果。 例如,不要调用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对编译器的restrict 。 由于我们没有将src传递给valonly() ,因此编译器可以假设它不会修改该值。

C语言的GNU方言允许使用__attribute__((pure))__attribute__((const))来声明函数没有副作用 ,允许这种优化而restrict ,但在ISO C11(AFAIK)中没有可移植的等价物。 当然,允许函数内联(通过将其放在头文件中或使用LTO)也允许这种优化,并且对于小函数更好,特别是如果在内部循环中调用。


编译器通常非常积极地进行标准允许的优化,即使它们对一些程序员来说是令人惊讶的并且打破了一些恰好工作的现有不安全代码。 (C是如此可移植,以至于很多东西都是基本标准中未定义的行为;大多数好的实现确实定义了标准留下的许多东西的行为作为UB。)C不是一种在编译器中抛出代码是安全的语言。它做你想要的,而不检查你是否正确地做(没有符号整数溢出等)


如果您查看x86-64 asm输出以编译您的函数(从问题中),您可以轻松地看到差异。 我把它放在Godbolt编译器资源管理器上

在这种情况下,对a进行restrict就足以让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__放在所有保证不与其他内容重叠的指针上


BTW, restrict只是C中的一个关键字。某些C ++编译器支持__restrict____restrict作为扩展,所以你应该#ifdef它在未知的编译器上。

以来

如前面的答案所述,您需要添加“限制”。 我还想评论你的情景“结果可能与a重叠”。 这不是编译器检测到“a”可能发生变化的唯一原因。 它也可以被另一个具有指向“a”的指针的线程改变。 因此,即使您的函数没有更改任何值,编译器仍将假定“a”可能会更改。