【问题标题】:Same code compiled with different versions provides different result使用不同版本编译的相同代码提供不同的结果
【发布时间】:2017-01-18 13:43:18
【问题描述】:

我正在使用一个我无法控制也不知道其内部结构的外部库(我们称之为专有回调)。

我知道我有一个名为 callbacks 的类,它有两个 bool 变量:

class callbacks : public proprietarycallbacks {
  bool a = false;
  bool b = false;
  virtual callbackHandler() {
    cout "callback received\n";
    b = true;
  }
}

然后我有另一个继承自回调类的类:

class MyObject : public callbacks {

  void test() {
    while (!b) {
      cout << "test " << a << " " << b << endl;
      usleep(100000);
    }
  }
}

此代码在两个不同的 linux 版本中正确编译,有两个不同的 GCC 版本和 LIBC 版本。

在最近的一个(linux mint,GCC 5.4 LIBC 2.23)上,我运行应用程序,在 while 中查看 cout,当调用回调时,代码存在 while。

在较旧的版本(debian、GCC 4.9.2、LIBC 2.19)上,虽然从不存在,但变量始终为 false,即使我可以从回调内部看到打印。

我构建代码的方式和变量继承是否有问题,或者这与我使用的软件版本有关?

感谢您的宝贵时间

【问题讨论】:

  • b的设置和a的测试是不是错字?回调和循环也是在同一个线程上吗?
  • 如果你把变量改成std::atomic_bool,区别会消失吗?
  • (假设前面提到的错字实际上是一个错字)假设test()callbackHandler()是从不同的线程调用的,您将对a进行并发读写,其中,总之,不能保证按预期工作。您可以按照建议看到std::atomic_bool 发生了什么,甚至只是将a 声明为volatile;很可能一个编译器版本决定 a 在循环中没有改变并继续从例如一个寄存器而不是内存。
  • @jdehesa volatile 不是用来解决线程问题的。
  • @RichardCritten 是的,但我认为在这种情况下它会产生足够的差异,让两个编译器生成的程序行为相似(并揭示为什么以前不是这种情况)。当然,对于实际应用来说,这可能还不够。

标签: c++ linux gcc


【解决方案1】:

从您的test() 方法中可以明显看出涉及多个执行线程。 test() 在一个执行线程中,回调被另一个执行线程调用。

在另一个执行线程中设置bool标志,和在test()执行线程中读取相同的标志是没有顺序的。

通常的解决方案是使用std::atomic_bool;或者使用std::mutex 来实现排序和访问变量,或者在一个执行线程中设置它们,或者在另一个执行线程中读取它们的当前值,只有在互斥锁被锁定时。

【讨论】:

  • 您能详细说明一下吗?我同意缺少互斥体意味着我可以在更改值时读取具有不正确值的变量。尽管如此,在下一次 while 迭代中,read 不应该有更新的值吗?
  • @BlunT:为什么要这样?
  • 在写入的同时进行读取时,我知道正在读取的值不正确。但是在随后的读取中,不应该从同时更新的对象中检索值吗?那为什么?该值是否缓存了未定义的时间?
  • 允许 C++ 编译器生成任何以不改变可观察结果的方式优化的代码。 int a=b+c; int d=b+e; 允许 C++ 编译器生成仅读取一次 b 的代码,即使它被使用了两次。这在结果中没有可观察到的变化,因为没有任何改变 b 的值。 test() 中并没有改变b 的可观察值,因此允许编译器生成读取一次b 的代码,如果true 从函数返回,否则进入无限循环。跨度>
  • @SamVarshavchik 好的,在这种情况下,我如何告诉编译器从我的代码中解放出来? :) 我怎样才能让它每次都重新读取这些变量的值?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-08-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多