2

我有一个程序在 gcc 上几乎立即结束-O0,但在 gcc 和-O3. 如果我删除[[gnu::pure]]函数属性,它也会立即退出,即使该函数没有修改全局状态。该程序位于三个文件中:

thread.hpp

#include <atomic>

extern ::std::atomic<bool> stopthread;

extern void threadloop();
[[gnu::pure]] extern int get_value_plus(int x);

thread.cpp

#include <thread>
#include <atomic>
#include "thread.hpp"

namespace {
::std::atomic<int> val;
}

::std::atomic<bool> stopthread;

void threadloop()
{
   while (!stopthread.load())
   {
      ++val;
   }
}

[[gnu::pure]] int get_value_plus(int x)
{
   return val.load() + x;
}

main.cpp

#include <thread>
#include "thread.hpp"

int main()
{
   stopthread.store(false);
   ::std::thread loop(threadloop);

   while ((get_value_plus(5) + get_value_plus(5)) % 2 == 0)
      ;
   stopthread.store(true);
   loop.join();
   return 0;
}

这是编译器错误吗?缺乏正确使用注意事项的文档[[gnu::pure]]?对文档的误读导致[[gnu::pure]]我编写了一个错误?

4

2 回答 2

2

我有一个程序几乎立即在 gcc 上结束-O0,但在 gcc 和-O3

是的,因为启用优化后程序会被编译为无限循环。

这是编译器错误吗?缺乏正确使用注意事项的文档[[gnu::pure]]?对文档的误读导致[[gnu::pure]]我编写了一个错误?

这不是编译器错误。get_value_plus不是pure函数:

[[gnu::pure]] int get_value_plus(int x)
{
    return val.load() + x;
}

因为返回值可以随时更改(对于相同的x),因为val预计会被其他线程修改。

然而,编译器认为get_value_plus总是返回相同的值,将执行CSE,因此会假设:

while ((get_value_plus(5) + get_value_plus(5)) % 2 == 0);

可以写成:

int x = get_value_plus(5);
while ((x + x) % 2 == 0);

实际上,无论 的值如何,它都是一个无限循环x

while (true);

有关详细信息,请参阅GCC 文档。pure

一般来说,除非很好理解,否则避免使用优化提示!

在这种情况下,误解是pure允许函数读取全局内存,但如果该内存在调用者以外的其他人的调用中发生变化,则不允许:

但是,使用 pure 属性声明的函数可以安全地读取任何非易失性对象,并以不影响其返回值或程序可观察状态的方式修改对象的值。

于 2019-02-22T04:23:23.817 回答
-1

事实证明,我误读了文档。从关于puregcc 中属性的在线文档:

pure 属性禁止函数修改可通过检查函数返回值以外的方式观察到的程序状态。但是,使用 pure 属性声明的函数可以安全地读取任何非易失性对象,并以不影响其返回值或程序可观察状态的方式修改对象的值。

和不同的段落:

纯函数的一些常见示例是 strlen 或 memcmp。有趣的非纯函数是具有无限循环的函数或依赖于易失性内存或其他系统资源的函数,它们可能会在连续调用之间发生变化(例如多线程环境中的标准 C feof 函数)。

这两段清楚地表明我一直在对编译器撒谎,而我编写的函数并不符合“纯”的条件,因为它依赖于一个随时可能发生变化的变量。

我问这个问题的原因是因为这个问题的答案:GNU C 中的 __attribute__((const)) vs __attribute__((pure))根本没有解决这个问题(当时我还是问了我的问题)。最近的 C++ 周刊有一篇评论询问线程和纯函数。所以很明显那里有些混乱。

因此,符合此标记条件的函数的标准是它不能修改全局状态,尽管它可以读取它。但是,如果它确实读取了全局状态,则不允许读取任何可能被认为是“易失性”的全局状态,这最好理解为可能在两个立即连续调用函数之间发生变化的状态,即如果它的状态在这样的情况下阅读可能会发生变化:

f();
f();
于 2019-02-22T16:27:09.883 回答