g++-8 和更早版本之间的奇怪行为

Posted

技术标签:

【中文标题】g++-8 和更早版本之间的奇怪行为【英文标题】:Strange behavior between g++-8 and earlier versions 【发布时间】:2019-03-27 14:27:53 【问题描述】:

最近在将我们的应用程序从 gcc-5.3 移植到 8.2 时,我们注意到一个奇怪的行为破坏了我们的应用程序。

简而言之,gcc-8.2 似乎删除了我们的“比较 2 个无符号整数的 if 分支”之一,甚至没有产生警告。

我们用相同的编译选项尝试了 g++ 5.3、g++ 7.4 和 g++ 8.2,只有 g++ 8.2 有这个问题。下面将显示一个简短的示例。

#include <iostream>
#include <cstdint>
#include <cstdlib>
#include <cstring>

using namespace std;

struct myunion 
    myunion(uint32_t x) 
        _data.u32 = x;
    
    uint16_t hi() const  return _data.u16[1]; 
    uint16_t lo() const  return _data.u16[0]; 
    union 
        uint16_t u16[2];
        uint32_t u32;
     _data;
;

 __attribute__((noinline)) void printx1x2(uint32_t x1, uint32_t x2) 
    cout << "x1: " << x1 << endl;
    cout << "x2: " << x2 << endl;


__attribute__((noinline)) int func(uint32_t a, uint32_t b) 
    const uint32_t x1 = myunion(a).hi() * myunion(b).lo();
    const uint32_t x2 = x1 + myunion(a).lo() * myunion(b).hi();
    printx1x2(x1, x2);
    int ret = 0;
    if ( x2 < x1 ) 
        ret = 0x10000;
    
    return ret;


int main(int argc, char** argv) 
    cout << func(4294967295, 4294917296) << endl;
    return 0;

以上代码编译如下:

$ g++-7 --version
g++-7 (GCC) 7.4.1 20181207
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ g++-7 -Wall -std=c++14 -O3 a.cxx -o 7.out
$ ./7.out
x1: 1018151760
x2: 1018020689
65536

$ g++ --version
g++ (GCC) 8.2.1 20181127
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ g++ -Wall -std=c++14 -O3 a.cxx -o 8.out
$ ./8.out
x1: 1018151760
x2: 1018020689
0

我希望7.out 的输出是正确的。

这实际上是 UB(未定义行为)还是 g++ 错误?

更新

看起来移除联合访问 UB 仍会处理不需要的结果:

#include <iostream>
#include <cstdint>
#include <cstdlib>
#include <cstring>

using namespace std;

struct myunion2 
    myunion2(uint32_t x) 
        _data = x;
    
    uint16_t hi() const  return (uint16_t)((_data & 0xFFFF0000) >> 16); 
    uint16_t lo() const  return (uint16_t)((_data & 0xFFFF)); 
    uint32_t _data;
;

 __attribute__((noinline)) void printx1x2(uint32_t x1, uint32_t x2) 
    cout << "x1: " << x1 << endl;
    cout << "x2: " << x2 << endl;


__attribute__((noinline)) int func(uint32_t a, uint32_t b) 
    const uint32_t x1 = myunion2(a).hi() * myunion2(b).lo();
    const uint32_t x2 = x1 + myunion2(a).lo() * myunion2(b).hi();
    printx1x2(x1, x2);
    int ret = 0;
    if ( x2 < x1 ) 
        ret = 0x10000;
    
    return ret;


int main(int argc, char** argv) 
    cout << func(4294967295, 4294917296) << endl;
    return 0;

输出:

$ g++-7 -Wall -std=c++14 -O3 a.cxx -o 7.out
[2019-03-27 22:48:30][wliu@wliu-arch-vm1 ~/tests]
$ ./7.out
x1: 1018151760
x2: 1018020689
65536
[2019-03-27 22:48:32][wliu@wliu-arch-vm1 ~/tests]
$ g++ -Wall -std=c++14 -O3 a.cxx -o 8.out
[2019-03-27 22:49:11][wliu@wliu-arch-vm1 ~/tests]
$ ./8.out
x1: 1018151760
x2: 1018020689
0

【问题讨论】:

读取工会的非活动成员是并且一直是未定义的行为。见this en.cppreference.com/w/cpp/language/union 标准不用开,cppreference 告诉你是UB。 This 答案不是完全骗人的,但它足够接近相关 啊哈。谢谢大家! 最后this 是个骗子并且有一些相关信息。 【参考方案1】:

问题(除了原始示例中的联合双关语)是这个表达式:

myunion2(a).lo() * myunion2(b).hi();

操作数的值为65535 * 65535。操作数的类型为uint16_t

对小于int 的类型不执行算术运算。较小的类型首先被提升。由于uint16_t小于int,并且uint16_t可以表示的值的范围可以用int表示,所以这些操作数被提升为int。但是操作 65535 * 65535 溢出了int,这是一个有符号类型。并且有符号溢出具有未定义的行为。

解决方案:在乘法之前转换为更大的无符号(或首先返回更大的无符号):

const uint32_t x1 = (unsigned)myunion2(a).hi() * myunion2(b).lo();
const uint32_t x2 = x1 + (unsigned)myunion2(a).lo() * myunion2(b).hi();

【讨论】:

我认为 hi/low 应该只返回一个 uint32_t 以(可能?imo?)更优雅的方式解决这个问题。 @MikeVine 从技术上讲,uint32_t 在具有 64 位 int 的系统上仍会导致升级为 int。但是对于这些计算,64 位的int 不会溢出,所以也许可以。我确实提到返回较大的类型是一种选择。是否更优雅是主观的。函数的用户可能不一定总是需要较大的类型来满足他们的目的。 你救了我的命!感谢您提供高度紧凑且内容丰富的解释!

以上是关于g++-8 和更早版本之间的奇怪行为的主要内容,如果未能解决你的问题,请参考以下文章

MinGW中预处理器g ++的奇怪行为

iOS7 中选定 UITableViewCell 的奇怪行为

优胜美地 Xcode 6.3.2 和更早的 osx 版本

启用优化的g ++和clang ++的奇怪行为[重复]

无法打开数据库,因为它是版本 904。此服务器支持版本 852 和更早版本。不支持降级路径[关闭]

iOS6 中奇怪的 SplitView/NavigationController 行为