Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/144.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 打破int和float之间严格混叠的实际后果_C++_Strict Aliasing - Fatal编程技术网

C++ 打破int和float之间严格混叠的实际后果

C++ 打破int和float之间严格混叠的实际后果,c++,strict-aliasing,C++,Strict Aliasing,我曾经在一个我必须维护的项目中有一个非常微妙的错误。本质上,它是这样做的: union Value { int64_t int64; int32_t int32[2]; int16_t shorts[4]; int8_t chars[8]; float floats[2]; double float64; }; Value v; // in one place (not sure about exact code, it could be jus

我曾经在一个我必须维护的项目中有一个非常微妙的错误。本质上,它是这样做的:

union Value {
    int64_t int64;
    int32_t int32[2];
    int16_t shorts[4];
    int8_t chars[8];
    float floats[2];
    double float64;
};

Value v;
// in one place (not sure about exact code, it could be just memcpy):
v.shorts[0] = <some short value>;
v.shorts[1] = <some other short value>;
// in another place:
float f = v.floats[0];
联合值{
int64_t int64;
int32_t int32[2];
int16_t短裤[4];
国际字符[8];
浮动浮动[2];
双浮64;
};
v值;
//在一个地方(不确定确切的代码,可能只是memcpy):
v、 短裤[0]=;
v、 短裤[1]=;
//在另一个地方:
浮动f=v.浮动[0];
现在,就标准而言,这只是UB。实际上,这可能意味着什么,但我很难想象一个合理的实现会导致上述代码引发第三次世界大战或使我的电脑崩溃。在现实生活中,我只能想象发生两件事:

  • 编译器可能会在优化过程中出错,而没有意识到它在这里处理的是相同的内存。在这种情况下不太可能,因为写入和读取发生在完全不同的地方

  • 没有什么不好的事情发生,浮点值只是一点一点地读取

  • 实际上,除了一次之外,几乎总是情况2。在大约100-150个输入文件上以发布模式运行MSVC 2010编译的程序后,在其中一个文件中,它生成了一个不正确的值,该值与根据常识应该的值相差一位。这也是非常重要的一点,所以我没有说,
    1.5
    ,而是得到了类似
    117.9
    。我能够追踪到精确的读取,在修复代码以遵守严格的别名规则之后,一切都很好

    现在的问题是,纯粹从低层次的角度来看,是什么导致了这种情况?CPU处理浮点值的一些特性?硬件缓存细节?编译器怪癖?为什么只有一个值是错误的


    硬件是一些旧的2核64位Intel CPU,运行32位Windows 7(如果有帮助的话)。该程序是一个单线程控制台应用程序,没有什么特别之处。这个问题是100%可重复的,相同的输入文件总是产生相同的输出,而错误的总是相同的值。

    从标准的角度来看,代码
    v.shorts[0]=something获取“short*”类型的指针值,加零,并使用结果指针存储值。我认为C89的作者打算,在这种情况下,使用别名的高质量实现可以识别别名,但标准文字中没有任何要求。请注意,当这些规则包含在C89中时,编译器只希望在非常局部的级别上应用它们;此外,这些规则通常不会给程序员带来严重的问题,除非它们应用于更深远的层面。不幸的是,一些编译器正在积极地寻求尽可能扩大规则的范围

    如果要将每个数组放置在联合体中的单独结构中,然后执行以下操作:

    v.floats.arr[0] = value;
    v.floats.arr[1] = value;
    v.floats = v.floats; // Compiler knows that float* may alter float members,
                         // and that writing member of union may alter other
                         // members
    
    。。。现在用其他东西

    编译器应该有希望认识到v.float的赋值需要
    不生成任何代码,但合格的编译器仍必须将其视为工会其他成员可能已被修改的适当通知。但是,请注意,从6.2开始,gcc中的模式似乎不可靠;在某些情况下,当生成任何代码都不需要赋值时,编译器将完全忽略赋值(包括其别名含义)。但是,我看不出有任何理由对gcc的错误行为进行反向处理——只要使用-fno严格的别名就可以了,除非或直到gcc的别名逻辑得到修复。

    您真的需要查看程序集输出;在语言层面上推理UB是疯狂的,在汇编层面上推理UB只是愚蠢的;)@TartanLlama,那是很久以前的事了,我不再有确切的代码和输入,所以我认为我无法复制它。我想从低级的软件和硬件专家那里获取更多的理论知识。不是什么原因造成的,而是什么原因造成的。我想,一个正在进行大量别名分析的编译器可能会注意到,联合的
    floats
    部分从未初始化过,因此该对象不是活动的,因此只会给您一些未初始化的内容。LLVM有
    undef
    用于在IR中标记此项,例如。@TartanLlama,读取未初始化值的概率是多少,该值仅在一位上与正确值不同?2^-31左右?不太可能。有趣的信息,但不能完全回答我的问题。我知道很多事情都有可能发生。我不明白的是,会发生什么样的事情,会导致某个特定值出现1位错误。它与GCC配合得很好,只有MSVS2010在发布模式下才会产生这种奇怪的效果。@SergeyTachenov:优化很奇怪。了解在错误读取期间,哪些值被写入了与
    float
    重叠的所有内容中,哪些值是预期值,哪些值是接收到的,这将很有帮助。有时,优化可以改变写操作和读操作,因此,如果编译器认为顺序不会对事情产生不利影响,则可能会在机器代码的早期出现在以逻辑执行顺序读取后写入的某些值。我不指望MSVC2010会这样做,但有些事情正在以出乎意料的方式发生。@SergeyTachenov:无论如何,不管gcc目前是否行为不端,我认为重要的是要知道,gcc的作者已经公开声明,一段代码在今天的gcc上以某种方式工作的事实并不构成任何承诺,即如果代码依赖于任何超出标准(作者对标准的解释)规定的行为,那么明天的版本不会破坏它。