GCC对纯函数的优化

GCC optimization of pure functions

我对 GCC 关于优化 pure 函数的保证感到困惑(来自 online docs):

pure

Many functions have no effects except the return value and their return value depends only on the parameters and/or global variables. (...)

Interesting non-pure functions are functions with infinite loops or those depending on volatile memory or other system resources, that may change between two consecutive calls (such as feof in a multithreading environment).

对于const

const

Many functions do not examine any values except their arguments, and have no effects except the return value. Basically, this is just slightly more strict class than the pure attribute below, since the function is not allowed to read global memory.

Note that a function that has pointer arguments and examines the data pointed to must not be declared const. Likewise, a function that calls a non-const function usually must not be const.

因此,我尝试创建一个接受指针参数的函数,并尝试标记它 pure。但是,我尝试使用 GCC 在线编译此函数(我尝试了 constpure):

typedef struct
{
    int32_t start;
    int32_t end;
}
Buffer;

inline __attribute__((pure,always_inline)) int32_t getLen(Buffer * b) 
{
    return b->end - b->start;
}

并注意到 GCC(至少我试过的几个 online compiler versions):

    如果传递的 Buffer* 参数指向全局值,
  1. 不会优化 对该函数的调用(即调用它多次),
  2. 优化 调用此函数(即仅调用一次),如果 传递的指针指向本地(堆栈)变量。
  3. 即使我将函数标记为 const 而不是 pure,这两种情况的工作方式相同,但如果有指针参数,大概 const 会被忽略?

这是一件好事,因为全局 Buffer 可能随时更改为不同的 thread/interrupt,而局部 Buffer ] 对于优化来说是绝对安全的。

但是关于传递指针的评论让我完全困惑。是否有地方明确定义了 GCC 的行为 pure 接受指针参数的函数?

"the guarantees GCC makes".

没有。 pureconst 属性是 you 对 GCC 做出的 promises

当您作出这些承诺时,优化器可能能够建立某些变量的非别名。但即使在可能的情况下,非混叠也可能与优化无关(因为它不够充分或无关紧要)。添加 pureconst 就没关系了。

Is there a place where the behavior of the GCC is explicitly defined for pure functions accepting pointer arguments?

行为与不接受指针参数的纯函数没有区别;他们可以读取程序中的任何内存,但不能写入内存或执行 IO。

您将 pure 和 const 函数编写为内联函数,这让事情变得非常混乱;如果函数体在调用点对编译器可用,它可以自己计算出被调用函数执行哪些操作。 pure 和 const 属性在函数体不可见的情况下最有用,因为它将在单独的编译单元中定义。

例如,考虑以下非纯函数、纯函数和 const 函数的集合:

__attribute__((__pure__)) int a();
__attribute__((__const__)) int b();
void c();

如果我们连续调用 a 两次,不插入任何操作,我们可以将其折叠为一次调用,因为 a 保证访问全局内存只是为了读取它;即使另一个线程大约同时写入全局内存,a 也无法与该线程通信,因此编译器可以假定写入发生在两次调用 a 之前或之后:

int f() {
    int i = a();
    i += a();
    return i;  // optimized to "return a() * 2;"
}

如果我们在对 a 的调用之间调用 c,我们必须假设 a 的 return 值会受到对 [=18= 的调用的影响]:

int g() {
    int i = a();
    c();
    i += a();
    return i;  // no optimization possible
}

但是,如果我们调用 const b 而不是纯 a,我们可以将其折叠为单个调用,因为 b 无法读取 [=18] 的任何内存=] 可能会写信给:

int h() {
    int i = b();
    c();
    i += b();
    return i;  // optimized to "c(); return b() * 2;"
}