代码之家  ›  专栏  ›  技术社区  ›  Anteru

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

  •  19
  • Anteru  · 技术社区  · 16 年前

    只是想知道:当我对一个指针添加限制时,我告诉编译器该指针不是另一个指针的别名。假设我的函数如下:

    // 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 每次都要重新蚀刻。但是,作为 被标记 const 编译器也可以假定a是固定的,因此一旦获取它就可以了。

    问题是,在这种情况下,使用限制的建议方法是什么?我当然不想让编译器重写 每次,但我都找不到关于如何 restrict 应该在这里工作。

    5 回复  |  直到 7 年前
        1
  •  13
  •   falstro    16 年前

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

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

        2
  •  8
  •   Craig    11 年前

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

    宣言 const float* a 一个常量指针,它是常量存储。指针仍然是可变的。 float *const a 是指向可变浮点的常量指针。

    所以问题应该是 float *const restrict a (或) const float *const restrict a 如果你愿意的话)。

        3
  •  8
  •   Community Nick Dandoulakis    7 年前

    是的,你需要限制。 指向const的指针并不意味着没有任何东西可以更改数据,只是不能通过该指针更改数据。 .

    const 基本上只是一种要求编译器帮助您跟踪您希望函数可以修改哪些内容的机制。 康斯特 不是向编译器承诺函数确实不会修改数据 .

    不像 restrict ,使用指向的指针- 康斯特 对于可变数据基本上是对其他人的承诺,而不是对编译器的承诺。铸造掉 康斯特 到处都不会导致优化器(afaik)的错误行为,除非您尝试修改编译器放入只读内存中的内容(请参见下面关于 static const 变量)。如果编译器在优化时看不到函数的定义,则必须假定它会丢弃 康斯特 并通过该指针修改数据(即函数不尊重 康斯特 指针参数的状态)。

    编译器确实知道 static const int foo = 15; 但是,无法更改,并且即使将其地址传递给未知函数,也将可靠地内联该值。(这就是为什么 静态常量int foo=15; 不慢于 #define foo 15 对于优化编译器。优秀的编译器会像 constexpr 如果可能的话。)


    记住 限制 向编译器承诺,通过该指针访问的内容不会与其他内容重叠 . 如果这不是真的,那么您的函数就不一定会实现您所期望的。不要打电话 foo_restrict(buf, buf, buf) 就地操作。

    以我的经验(在GCC和Clang方面) 限制 主要用于存储通过的指针。放进去不疼 限制 也在源指针上,但是通常情况下,如果 全部的 您的功能所涉及的存储 限制 指针。

    如果循环中有函数调用, 限制 在源指针上,clang(而不是gcc)可以避免重载。见 these test-cases on the Godbolt compiler explorer 特别是这一个:

    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 一次,并保持调用中的值在函数调用之间保留寄存器。没有 限制 因为被调用的函数可能(作为副作用)修改 *SRC 通过另一个指针。

    例如,此函数的调用方可能传递了全局变量的地址 . 但是任何修改 *SRC 除了通过 SRC 指针会违反承诺 限制 对编译器生成。因为我们没有通过 SRC valonly() 编译器可以假定它不修改值。

    c的GNU方言允许使用 __attribute__((pure)) or __attribute__((const)) to declare that a function has no side-effects ,允许此优化 限制 但是在ISO C11(Afaik)中没有可移植的等效物。当然,允许函数内联(通过将其放在头文件中或使用LTO)也允许这种优化,并且对于小函数(尤其是在循环内部调用时)更好。


    编译器通常在做标准允许的优化方面非常积极,即使它们让一些程序员感到惊讶,并且破坏了一些发生在工作中的现有不安全代码。(C是如此可移植,以至于许多东西在基本标准中都是未定义的行为;大多数好的实现确实定义了许多标准保留为ub的东西的行为。)C不是一种可以安全地向编译器抛出代码的语言,直到它按照您的需要执行,而不检查您是否按正确的方式执行(不使用signed intEger溢出等)


    如果您查看用于编译函数的x86-64ASM输出(从问题中),您可以很容易地看到不同之处。我把它穿上 the Godbolt compiler explorer .

    在这种情况下,将 限制 a 足以让叮当声提升 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
    

    VS

    # 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__ 在所有保证不会与其他事物重叠的指针上 .


    顺便说一句, 限制 C是C++编译器中的一个关键字 限制条件 __restrict 作为扩展,所以你应该 #ifdef 它远离未知的编译器。

    自从

        4
  •  7
  •   Ivan Vučica    11 年前

    C-99标准 有一些例子 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 那么,它们确实不是多余的。

        5
  •  1
  •   Zig    15 年前

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