没有union的hashcode的内存地址

时间:2014-08-28 09:38:09

标签: c++ hash hashcode unions

在学习数据结构,特别是哈希表时,我们被告知为数据类型发明有效的哈希函数是一项非常艰巨的任务,但有人建议存在一个快速的快捷方式。也就是说,如果我们可以假设对象不在内存中移动,并且我们可以将对象相等定义为具有相同的内存地址(使用引用相等而不是值相等),那么我们可以获得如下对象的哈希码:

#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;
}

所以我的问题是:

  • 使用哈希码的内存地址是否有任何问题(再次假设引用相等是所需的行为)?
  • 鉴于使用联合进行转换是undefined behaviour,我们如何将内存地址转换为整数?
  • (请注意我在上面的代码中指出的任何其他错误.C ++指针很容易出错。)

4 个答案:

答案 0 :(得分:4)

  • 不,没有错;散列是一个整数,保证每个对象都是唯一的,这样可以减少碰撞的可能性。
  • 将指针投射到uintptr_t。不需要工会。此外,uintptr_t具有适合平台的大小,因此您不再需要使用int32_t等等。

    uintptr_t hash(const T &r)
    {
        return uintptr_t(&r);
    }
    

(如果散列必须是32位,则将其转换为uint32_t,或者在64位平台上,使用appropriate magic合并两半。)

答案 1 :(得分:2)

首先,您的代码假定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都是素数。您可以使用按位xor +

替换^

或任何技巧正确混合地址的位。

当然,YMMV并且绝对是系统特定的(例如,取决于您的系统是否有ASLR

对于某些class T,另一种方法可能是在构造函数中生成-in,例如在T实​​例的构造时,一些随机散列(使用快速 PRNG,如lrand48 ...),并将该散列作为私有实例成员变量。或者只是使用一些静态计数器,为每个实例唯一编号,并将该数字用作哈希值。

重要的是要确保所有实例都不同!如果你想对内容进行哈希处理,情况并非如此(但请参阅memoization等等。)。

另外,我不同意你的老师:确实,发明一个非常好的哈希函数很难,但是制作一个“足够好”的平均散列函数通常很容易(例如使用与素数的线性组合组成部分的哈希系数,见Bezout's theorem)。而通常,在 practice 中,这种“简单”的哈希函数运行得很好(当然也有例外情况,不可能的最坏情况可能很糟糕)。

另请阅读perfect hash,例如使用GNU gperf

答案 2 :(得分:0)

如果你想要引用相等,那么使用地址作为哈希码没有任何问题。但是,实现它比工会更实用的方法是使用intptr_t。如果intptr_t大于int32_t,您可以与-1一起使用,然后static_cast添加到uint32_t

答案 3 :(得分:0)

您只需要一个功能模板。 以下是您可以做的(假设您的哈希表具有有限数量的存储桶):

template <typename T>
uintptr_t hashcode(const T &obj, size_t size) {
    return reinterpret_cast<uintptr_t>(&obj) % size;
}