1

从 C++14 升级到 C++17(Ubuntu 18.04,GCC 7.5.0)时,我们一直在努力解决这个非常奇怪的问题。该工具链是 Linaro 在 Jetson TX2 上的,是​​默认的。

背景:

我们有一个 C++ 应用程序,它使用我们在 Ubuntu 18.04 上开发的A库中的算法。构建和广泛的系统测试已经运行了L两年。IntelJetson TX2

现在我们决定升级到 C++17(-std=c++1z with GCC)。我们首先在启用 C++17 的情况下进行构建L,起初一切似乎都运行良好,但后来我们注意到一些测试运行开始在 ARM 上出现奇怪的行为。就像 30 个测试中的 2 个测试一样,这是确定性的(!)。

然后我们开始调查并注意到库中的一个构造函数接受const std::pair<float, float> &了某种损坏的数据。内部构造函数.first似乎是.second并且.second总是0。像这种奇怪的东西。

因此,如果A仍在 C++14 上并且L在 C++17 上,就会发生这种情况。

好的。

然后我们反过来尝试了这个。L在 C++14 和AC++17 上的应用程序。结果是相似的。一些测试开始失败(虽然不一样)并且它是确定性的。根本原因还是一样的:不知何故std::pair<float, float>,API 搞砸了。

所以到目前为止的组合是这样的:

A: C++14, L: C++14, Intel => OK

A: C++14, L: C++17, Intel => OK

A: C++17, L: C++14, Intel => OK

A: C++17, L: C++17, Intel => OK

A: C++14, L: C++14, ARM => OK

A: C++14, L: C++17, ARM => FAIL

A: C++17, L: C++14, ARM => FAIL

A: C++17, L: C++17, ARM => OK

显然这是一个大型商业应用程序,所以我不能在这里复制粘贴代码。我首先怀疑这将是一个编译器错误(它仍然可能是),但它似乎太明显了!

还有更多:

我们最近还注意到,如果我们只用const std::pair<float, float> &简单float的参数替换 ,测试就会再次通过。

任何猜测到底是怎么回事?编译器错误?切换到 C++17甚至在理论上会导致这样的事情(编译器完全相同)?尤其是这样(升级哪个组件都没有关系)。

我们只是没有发现 API 有什么问题。它已经使用 C++14 在 Intel 和 ARM 上运行了将近两年,没有任何问题。

编辑:设法制作一个工作示例项目: https ://drive.google.com/open?id=1B5SceFB1mKkCnE8iE59Mq0lScK2F0iOl

说明和示例输出README.md

此示例在 Intel 和 Jetson TX2 上的输出:

On Intel (Ubuntu 18.04, GCC 7.5.0) this app prints:

$ ./app/App 
S: 42
L: 3.14
R: 666
In Foo::update(): s: 42
In Foo::update(): l: 3.14
In Foo::update(): r: 666

On Jetson TX2 (Ubuntu 18.04, GCC 7.5.0 / Linaro) this app prints:

$ ./app/App 
S: 42
L: 0
R: 2.39152e+29
In Foo::update(): s: 42
In Foo::update(): l: 0
In Foo::update(): r: 2.39152e+29
4

2 回答 2

4

切换到 C++17甚至在理论上会导致这样的事情(编译器完全相同)?

从理论上讲,它可以通过多种方式改变某些东西。

最直接的是标准库头文件有很多条件编译,比如:

#if __cplusplus <= 201402L
/* code for C++14 ... */
#else
/* code for C++17 ... */
#endif

所需要的只是使这两位代码不兼容。我们非常努力地确保不会发生这种情况。但理论上它可能发生。

然后我们开始调查并注意到库中的一个构造函数接受const std::pair<float, float> &了某种损坏的数据。内部构造函数.first似乎是.second并且.second总是0。像这种奇怪的东西。

我无法重现这样的东西。当我检查 GCC 7.3 为 Aarch64 生成的程序集时,结果对于 C++14 和 C++17 是相同的。因此,您需要提供有关代码的更多信息。显示构造函数签名和构造函数的数据成员应该不难,而无需显示大量专有代码。

编辑:我已将工作示例简化为这个实时示例,该示例显示为具有空基的类生成的代码对于 C++14 和 C++17 是不同的,这是一个编译器错误:https://godbolt。 org/z/E46NFc

我报告为https://gcc.gnu.org/bugzilla/show_bug.cgi?id=94383

于 2020-03-26T22:20:30.357 回答
4

由于我没有看过,所以我不确定任何事情,但这听起来像是二进制接口更改的情况。ABI。这可能是由于结构布局更改而发生的,这可能是统一对和元组的努力的一部分。这也可能是填充规则的变化。或对齐规则。突然觉得这个可能性最大。如果它使用浮点对齐与双对齐进行分配,或者一方决定对所有内容使用 64 位对齐,那肯定会导致奇怪的事情。

通过引用传递在实现中传递一个指针。通常。因此,如果 C++ 版本之间的结构发生变化,它可能具有不同的字节布局。

这在 ARM 编译器中可能是一个意外,因为如果 ABI 故意更改,那么就会像 GNU libc++ 中的 C++11 std::string 那样将其放入一个新的命名空间中。

我会通过在每个编译器版本中制作 std::pairs 的结构和数组来测试其中的一些,并将它们转储到磁盘文件或在调试器中检查它们。查看哪些字节发生了变化。

于 2020-03-26T18:06:42.670 回答