如果一个指针已经标记为const,那么在C中限制帮助? [c++]


Answers

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 *是多余的,那么它们确实不是多余的。

Question

只是想知道:当我添加限制到一个指针,我告诉编译器,指针不是另一个指针的别名。 让我们假设我有一个功能,如:

// 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在这里工作的好消息。




是的,你需要限制。 指向常量并不意味着什么都不能改变数据,只是你不能通过指针来改变它

const通常只是一个机制,让编译器帮助你跟踪你想让函数被修改的东西。 const不是编译器的承诺,一个函数真的不会修改数据

restrict不同的是,使用指向const的可变数据基本上是对其他人的承诺,而不是编译器。 除非你试图修改编译器放在只读存储器中的东西(参见下面有关static const变量),否则在整个地方转换const不会导致优化程序(AFAIK)的错误行为。 如果编译器在优化时看不到函数的定义,那么它必须假设它抛出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放在你的源指针也没有什么坏处,但是如果你的函数所有的存储都是通过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哪个。 但是,因为铿锵这样做,我猜根据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编译器资源管理器上

在这种情况下,对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__放在所有保证不与别的东西重叠的指针上

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

以来