为什么“从’X *’转换为’Y’会失去精度”这是一个很难的错误,什么是遗留代码的合适修复
1.为什么?
像这样的代码过去常常起作用,它应该是什么意思。 编译器是否允许 (通过规范)使其成为错误?
我知道它正在失去精确度,我会很高兴收到警告。 但它仍然有一个明确定义的语义(至少对于未定义的缩小版本定义)并且用户可能想要这样做。
2.解决方法
我有遗留代码,我不想重构太多,因为它相当棘手并且已经调试过。 它做了两件事:
-
有时在指针变量中存储整数。 如果代码之前存储了一个整数,则代码仅将指针强制转换为整数。 因此,当演员阵容缩小时,溢出在现实中永远不会发生。 代码经过测试和运行。
当存储整数时,它总是适合普通的旧无符号,因此改变类型不被认为是一个好主意并且指针传递相当多,所以改变它的类型将有点侵入性。
-
使用地址作为哈希值。 一个相当常见的事情。 哈希表对于扩展类型没有任何意义。
代码使用普通的
unsigned
作为哈希值,但请注意,更常见的size_t
类型仍可能生成错误 ,因为无法保证sizeof(size_t)
> =sizeof(void *)
。 在具有分段内存和远指针的平台上,size_t
只需要覆盖偏移部分。
那么最具侵入性的合适解决方法是什么? 已知代码在使用不会产生此错误的编译器编译时工作,所以我真的想要进行操作,而不是更改它。
笔记:
void *x; int y; union U { void *p; int i; } u;
-
*(int*)&x
和up = x, ui
不等于(int)x
,与(void *)y
不相反。 在大端架构上,前两个将返回较低地址上的字节,而后一个将在低位字节上工作,这些字节可能位于较高地址上。 -
*(int*)&x
和up = 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
。