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

时间:2009-01-19 12:30:18

标签: c++ c optimization restrict-qualifier

只是想知道:当我向指针添加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应该如何在这里工作的好信息。

5 个答案:

答案 0 :(得分:15)

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

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

答案 1 :(得分:8)

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

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

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

答案 2 :(得分:8)

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

const主要是一种机制,要求编译器帮助您跟踪您希望允许修改哪些内容。 const并非对编译器的承诺,即函数确实无法修改数据

restrict不同,对可变数据使用指针 - const基本上是对其他人的承诺,而不是对编译器的承诺。抛弃const遍布整个地方不会导致优化器(AFAIK)的错误行为,除非您尝试修改编译器放入只读内存中的内容(请参阅下面有关{{1}的内容)变量)。如果编译器在优化时无法看到函数的定义,则必须假定它抛弃static const并通过该指针修改数据(即函数不尊重{{} 1}}它的指针args)。

编译器确实知道const无法更改,即使您将其地址传递给未知函数,也会可靠地内联该值。 (这就是优化编译器const不比static const int foo = 15;慢的原因。好的编译器会尽可能优化它static const int foo = 15;。)

请记住,#define foo 15是对编译器的承诺,即您通过该指针访问的内容不会与其他任何内容重叠。如果情况不对,那么您的功能肯定不会达到预期效果。例如请勿致电constexpr进行就地操作。

根据我的经验(使用gcc和clang),restrict主要用于存储的指针。将foo_restrict(buf, buf, buf)放在你的源指针上并没有什么坏处,但是如果 all 你的函数所做的商店是通过restrict指针。

如果循环中有任何函数调用,源指针上的restrict确实让clang(但不是gcc)避免重新加载。见these test-cases on the Godbolt compiler explorer,特别是这个:

restrict

gcc6.3(针对x86-64 SysV ABI)决定在函数调用中将restrict(指针)保留在调用保留寄存器中,并在调用后重新加载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__ } 。无论是gcc的算法都没有发现这种优化的可能性,或者认为它不值得,或者gcc devs故意没有实现它,因为他们认为它不是安全。 IDK哪个。但是由于clang这样做,我根据C11标准猜测它的可能合法。

clang4.0将此优化为仅加载src一次,并将值保存在函数调用中的调用保留寄存器中。如果没有*src,它就不会这样做,因为被调用的函数可能(作为副作用)通过另一个指针修改*src

此函数的调用者可能已传递全局变量的地址,例如。但是除restrict指针之外的任何*src修改都会违反*src对编译器的承诺。由于我们没有将src传递给restrict,因此编译器可以假设它不会修改该值。

C的GNU方言允许使用__attribute__((pure)) or __attribute__((const)) to declare that a function has no side-effects,允许在没有src的情况下进行优化,但在ISO C11(AFAIK)中没有便携式等效。当然,允许函数内联(通过将其放在头文件中或使用LTO)也允许这种优化,并且对于小函数更好,特别是如果在内部循环中调用。

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

如果您查看x86-64 asm输出以编译您的函数(来自问题),您可以轻松地看到差异。我把它放在the Godbolt compiler explorer 上。

在这种情况下,将valonly()放在restrict上就足以让clang提升restrict的负载,而不是gcc。

使用a,clang和gcc都会提升负载。

e.g。

a[0]

VS

float *restrict result

总而言之,# 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 放在所有保证不与其他内容重叠的指针上

BTW,# 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 只是C中的一个关键字。某些C ++编译器支持__restrict__restrict作为扩展,因此您应该__restrict__将它放在未知的编译器上。

答案 3 :(得分:7)

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

strtoimax和strtoumax函数

概要

#include <inttypes.h>
intmax_t strtoimax(const char * restrict nptr,
                   char ** restrict endptr, int base);
--- snip ---

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

答案 4 :(得分:1)

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