没有并集的哈希代码的内存地址
Memory address to hashcode without union
在学习数据结构,特别是哈希表时,我们被告知为数据类型发明一个有效的哈希函数是一项非常艰巨的任务,但有人建议存在一个快速快捷方式。也就是说,如果我们可以假设对象在内存中不移动,并且我们可以将对象相等定义为具有相同的内存地址(使用引用相等而不是值相等),那么我们可以获得这样的对象的哈希代码:
#include<iostream>
template<typename T>
class hashcoder {
private:
union impl {
T* ptr;
int32_t hashcode; // or int64_t on a 64-bit architecture = architecture with 64-bit pointers
};
public:
static int32_t hash(const T& r) {
impl i;
i.ptr = &r;
return i.hashcode;
}
};
class myclass {
// whatever
};
int main() {
myclass m;
std::cout << hashcoder<myclass>::hash(m) << std::endl;
}
所以我的问题是:
- 使用内存地址作为哈希代码有什么问题吗(再次假设引用相等是所需的行为)
- 考虑到使用并集进行转换是未定义的行为,我们如何将内存地址转换为整数
- (请随意指出我在上面代码中犯的任何其他错误。C++指针很容易出错。)
- 不,这没什么错;散列是一个整数,保证每个对象都是唯一的,这降低了冲突的概率
-
将指针投射到
uintptr_t
。不需要工会。此外,uintptr_t
具有适合平台的大小,因此您不再需要使用int32_t
等uintptr_t hash(const T &r) { return uintptr_t(&r); }
(如果散列必须是32位,则将其强制转换为uint32_t
,或者在64位平台上,使用适当的魔术将两半结合起来。)
首先,您的代码假设T
的不同实例总是不同的(对于!=
运算符)。这种情况并不总是如此,例如,当你想对字符串值(即内容)进行散列,而不是对某个地址进行散列时,std::string
肯定是错误的。。。。同样,如果你想散列整数的(数学)向量,你应该散列它们的内容(向量的数学组成部分)。
然后,您应该意识到,仅仅使用某个对象的地址作为其哈希可能是次优的,因为足够大的对象的地址往往是8或16字节的倍数(确切地说,是该对象类型的对齐方式),并且在同一时刻分配的对象地址通常非常相似。事实上,指针的一些"中间位"可能比低位或高位更"随机"。
一个稍微好一点的方法可能是对指针地址进行一些逐位运算,例如
static inline int32_t ptrhash(const void*p) {
uintptr_t u = (uintptr_t)p;
return u ^ (u >> 10);
}
或
static inline int32_t ptrhash(const void*p) {
uintptr_t u = (uintptr_t)p;
return (u * 200771) + (u % 300823);
}
200771和300823都是素数。您可以用逐位异或^
替换+
或者适当地混合地址的比特的任何技巧。
当然,YMMV和它绝对是系统特定的(例如,取决于您的系统是否有ASLR)
对于一些class T
,另一种方法可能是在构造函数中生成一些随机哈希(使用类似lrand48的quickPRNG…),并将该哈希作为私有实例成员变量。或者简单地使用一些静态计数器,对每个实例进行唯一编号,并将该编号用作哈希。
重要的一点是要确保您的所有实例都是不同的!如果你想对内容进行散列,情况并非如此(但请参阅记忆等…)
此外,我不同意你的老师:事实上,发明一个非常好的哈希函数很难,但制作一个"足够好"的平均哈希函数通常很容易(例如,使用与组成部分哈希的素数系数的线性组合,见Bezout定理)。通常,在实践中,这种"简单"的哈希函数工作得足够好(当然也有例外,最坏的情况可能令人敬畏)。
还请阅读有关完美散列的内容,例如使用GNU gperf。
如果您想要引用相等,那么使用地址作为哈希码没有错。然而,实现它的一种比联合更明智的方法是使用intptr_t
。如果intptr_t
大于int32_t
,则可以与-1进行AND运算,然后将static_cast
与uint32_t
进行AND运算。
您只需要一个函数模板。以下是你可以做的(假设你的哈希表有有限数量的桶):
template <typename T>
uintptr_t hashcode(const T &obj, size_t size) {
return reinterpret_cast<uintptr_t>(&obj) % size;
}
- 这个指针和内存代码打印是什么?我不知道是打印垃圾还是如何打印我需要的值
- 为什么示例代码访问IUnknown中已删除的内存
- 是否值得降低我的代码的可读性,以便在出现内存不足错误时提供异常安全性?
- 以下代码执行哪种内存分配(动态或静态)?
- 为什么以下C++代码中存在内存泄漏?
- 使用 g++7 构建的代码在访问未对齐的内存时崩溃
- 无法找出我的代码中的内存泄漏
- 存储在哪个内存段(代码/数据段)类(员工)中?
- 带有 GDB 调试器的 VS 代码内存视图
- 我有一个线程 1:EXC_BAD_ACCESS(代码 = 1,地址 = 0x8)错误.我认为这是由于内存管理不好.我可以
- 如何防止使用 std::shared_ptr 的代码中的内存泄漏
- 这行代码中的内存是如何分配"int **v = new int*[n]; "的?
- 在我的以下代码中获取 MLE(内存限制错误).尝试解决 ROUND C 2019(问题 A-摆动行走)启动问题
- 我在 2D 数组的动态内存分配中遇到了一些奇怪的代码C++? 请解释一下这是什么?
- 为什么 valgrind 报告两个内存分配,而我的代码只请求一个?
- 如何使用 gcc 通过命令行限制C++代码的内存使用量?
- std::array 模板实例会占用更多的代码内存吗?
- C++OpenMP代码内存泄漏
- 如何将常量放入代码内存中
- Bug在我的代码内存操作