为什么“从’X *’转换为’Y’会失去精度”这是一个很难的错误,什么是遗留代码的合适修复

1.为什么?

像这样的代码过去常常起作用,它应该是什么意思。 编译器是否允许 (通过规范)使其成为错误?

我知道它正在失去精确度,我会很高兴收到警告。 但它仍然有一个明确定义的语义(至少对于未定义的缩小版本定义)并且用户可能想要这样做。

2.解决方法

我有遗留代码,我不想重构太多,因为它相当棘手并且已经调试过。 它做了两件事:

  1. 有时在指针变量中存储整数。 如果代码之前存储了一个整数,则代码仅将指针强制转换为整数。 因此,当演员阵容缩小时,溢出在现实中永远不会发生。 代码经过测试和运行。

    当存储整数时,它总是适合普通的旧无符号,因此改变类型不被认为是一个好主意并且指针传递相当多,所以改变它的类型将有点侵入性。

  2. 使用地址作为哈希值。 一个相当常见的事情。 哈希表对于扩展类型没有任何意义。

    代码使用普通的unsigned作为哈希值,但请注意,更常见的size_t类型仍可能生成错误 ,因为无法保证sizeof(size_t) > = sizeof(void *) 。 在具有分段内存和远指针的平台上, size_t只需要覆盖偏移部分。

那么最具侵入性的合适解决方法是什么? 已知代码在使用不会产生此错误的编译器编译时工作,所以我真的想要进行操作,而不是更改它。


笔记:

 void *x; int y; union U { void *p; int i; } u; 
  1. *(int*)&xup = x, ui 等于(int)x ,与(void *)y 相反。 在大端架构上,前两个将返回较低地址上的字节,而后一个将在低位字节上工作,这些字节可能位于较高地址上。
  2. *(int*)&xup = x, ui都是严格的别名冲突, (int)x 不是

C ++,5.2.10:

4 – 指针可以显式转换为足以容纳它的任何整数类型。 […]

C,6.3.2.3:

6 – 任何指针类型都可以转换为整数类型。 […]如果结果无法以整数类型表示,则行为未定义。 […]

因此,如果int是32位且void *是64位,那么(int) p是非法的; C ++编译器是正确的给你一个错误,而C编译器可能会在转换时给出错误或发出一个具有未定义行为的程序。

您应该编写,添加一个转换:

 (int) (intptr_t) p 

或者,使用C ++语法,

 static_cast(reinterpret_cast(p)) 

如果要转换为无符号整数类型,请通过uintptr_t而不是intptr_t

这是一个很难解决的“一般”,因为“松散精度”表示你的指针大于你试图存储它的类型。在你的脑海中可能很“好”,但是编译器是关心的您将把int值恢复为指针,现在已经丢失了高32位(假设我们正在讨论32位int和64位指针 – 还有其他可能的组合)。

uintptr_t与系统上的指针大小兼容,因此通常可以通过以下方式克服实际错误:

 int x = static_cast(reinterpret_cast(some_ptr)); 

这将首先从指针强制大整数,然后将大整数强制转换为较小的类型。

回答C

将指针转换为整数是实现定义的。 你的问题是,你所谈论的代码似乎永远不正确。 并且可能只适用于int和指针都是32位的古代架构。

唯一应该转换而没有丢失的类型是[u]intptr_t ,如果它们存在于平台上(通常它们就是这样)。 这种uintptr_t哪一部分适合用于你的哈希函数很难说,你不应该对此做出任何假设。 我会选择类似的东西

 uintptr_t n = (uintptr_t)x; 

然后

 ((n >> 32) ^ n) & UINT32_MAX 

这可以在32位拱上进行优化,并且可以为您提供64位拱上所有其他位的跟踪。

对于C ++,基本上应该适用相同的,只需要转换为reinterpret_cast(x)