25

只是想知道:当我向指针添加限制时,我告诉编译器该指针不是另一个指针的别名。假设我有一个类似的功能:

// 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。但是,正如a所标记const的,编译器也可以假设 a 是固定的,因此获取它一次是可以的。

问题是,在这种情况下,使用限制的推荐方法是什么?我当然不希望编译器a每次都重新获取,但我找不到关于restrict应该如何在这里工作的好信息。

4

5 回答 5

16

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

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

于 2009-01-19T12:35:52.387 回答
12

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

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

所以问题应该是,有什么意义float *const restrict a(或者const float *const restrict a如果你愿意的话)。

于 2013-10-25T15:10:02.020 回答
10

是的,你需要限制。 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由于我们不传递srcvalonly(),编译器可以假设它不修改值。

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在未知的编译器上使用它。

自从

于 2017-04-28T02:46:08.067 回答
8

C-99 标准 (ISO/IEC 9899:1999 (E))中有const * restrict7.8.2.3 节中的示例:

strtoimax 和 strtoumax 函数

概要

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

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

于 2011-08-25T16:21:39.673 回答
1

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

于 2009-05-26T16:29:42.533 回答