reinterpret_cast 时值发生变化

3

使用reinterpret_cast将整数转换为浮点数时,内存内容会发生变化。

例如,

   float fDest = 0;
  __int32 nTempDest = -4808638;

  fDest = *reinterpret_cast<float*>(&nTempDest);

变量nTempest的十六进制表示为'42 a0 b6 ff',但是经过reinterpret_cast后,fDest的内容变为了'42 a0 f6 ff'。请问有人能解释一下为什么第三个字节从b6变成了f6吗?

4
确定需要证明(printf %x)和架构。 - Mikhail
3
该代码违反了严格别名规则,其行为是未定义的。当我尝试运行它时,得到了“-nan”的结果。 - Jesse Good
2
@user1610015,你混淆了未定义行为和未指定行为。后者具有特定的、实现定义的行为,但这段代码暴露了前者。 - Konrad Rudolph
1
@user1610015:不是的。编译器可以合法地使应用程序打印出“这里有龙”,并在调用未定义行为时仍符合规范。重点是任何事情都可能发生,特别是当您开始启动优化时,不变量被打破,优化往往会使事情变得非常糟糕。 - Martin York
1
@LokiAstari,你一开始说“不”,然后继续说一些与我所说的无关的东西。我只是说使用reinterpret_cast将内存解释为不同类型是常见且在大多数编译器中都是明确定义的(实际上这更多地与处理器架构有关)。这与标准符合性无关。 - user1610015
显示剩余12条评论
2个回答

2
在纯C++中,实际上这是未定义的行为。尽管如此,你所看到的有一个解释。
我假设你给出的十六进制表示来自内存的字节视图。显然,你在使用小端架构。因此,我们从32位数量开始的数值是0xffb6a042,它确实是-4808638的二进制补码表示。
作为IEC 60559单精度浮点数(也是32位),0xffb6a042是一个负的信号NaN。在这种表示中,NaNs的形式为(二进制表示)
s1111111 1qxxxxxx xxxxxxxx xxxxxxxx

这里的 s 是符号,x 是任意值,q=1 表示静默NaN,q=0 表示信号NaN。

现在你正在使用信号NaN,并将其赋值给 fDest。如果浮点信号处于活动状态,则会引发浮点无效异常。默认情况下,这些异常会被简单地忽略,并在传播时将信号NaN值“静音”。

因此,在将 NaN 赋值给 fDest 时,NaN 会被传播,并且实现通过设置第22位将其转换为静默NaN。这就是您观察到的更改。


1
你的代码产生了“未定义行为(UB)”。 reinterpret_cast 仅保证将一个指针类型转换为另一种类型并将其转换回原始指针类型后,您可以获得原始数据。除此之外的任何操作都会产生 UB[注1]
这是一种 UB,因为您不能依赖以下事实:
sizeof(float) == sizeof(nTempDest)

这并不保证在所有实现中都是true,对于遵循严格别名规则的实现来说绝对不是真的。如果实现不符合要求,则会导致未定义的行为。

[注1]这个规则有例外情况,如果你需要依靠这些边角规则,那么你就处于波涛汹涌的水域中,请务必确定自己在做什么。


这为什么是不正确的?任何技术原因的否决意见都会有所帮助。 - Alok Save
1
我不能确定因为我不是那个给出负评的人,但我猜测这可能是因为这不是唯一能够给出定义行为的事情。例如:“当类型为“指向T1的指针”的prvalue被转换为类型“指向cv T2的指针”时,如果T1和T2都是标准布局类型(3.9),并且T2的对齐要求不比T1更严格,或者其中一个类型是void,则结果是static_cast<cv void *>(v))。” - Jerry Coffin
@JerryCoffin:是的,我刚刚重新查看了规格,以检查其它定义的用法,并在§5.2.10.7中找到了它。我同意我的答案提到了使用reinterpret_cast的一般准则。是的,存在一些特殊情况,但这种情况绝对是未定义的行为。 - Alok Save
1
@AlokSave:是的,这个没错,但是“除此之外的任何东西都会产生未定义行为”就是错误的。 - Jerry Coffin
@NicolBolas:那个注释不是给标准专家的,而是为了普通用户,因为他们可能不像标准纯粹主义者那样理解标准术语。 - Alok Save
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接