来自K& R书籍的散列函数

时间:2013-02-15 03:07:11

标签: c hash x86 32-bit integer-overflow

考虑这个功能:

unsigned hash(char *s)
{
  char *p;
  unsigned hashval;
  for(p = s; *p; p++)
    hashval = *p + 31 * hashval;
  return hashval;
}

如何衡量s中返回错误结果的字节数,例如溢出? 我在32位平台上。

2 个答案:

答案 0 :(得分:5)

如果您将其更改为

unsigned hash(const char *s)
{
  const unsigned char *p;
  unsigned hashval = 0;
  for (p = (const unsigned char *) s; *p; p++)
    hashval = *p + 31u * hashval;
  return hashval;
}

然后由于整数溢出而不再存在任何未定义行为的可能性,因为算术中涉及的所有类型都是无符号的,因此所有内容都包含mod 2 n (其中 n unsigned的宽度(以位为单位)。我还修复了未初始化变量的使用,并制作了sp const,这可以改善优化和/或捕捉函数体中的错误。

(我现在不记得确切的算术转换规则;首先可能不可能。但是,用这种方式写它会使显然不可能。)

顺便说一句,现在已知有更好的哈希函数:如果你没有强烈的理由不这样做,我推荐使用SipHash

答案 1 :(得分:3)

有几点想法:

首先,在散列函数中需要溢出。

其次,由于你的函数包含一个31*hashval,并且字符串中的每个元素必须至少具有值1,你可以预期在命中溢出之前你可以拥有的最长字符串是一个字符串x01,当它达到6的长度时它会溢出散列(因为*31操作将整个数字分配到左边的5位,所以会有进位,这意味着你很可能影响第六位,并且6 * 6 = 36> 32)。当字节较大时,数字会更少(第一个字节几乎定义了行为 - 当它很大时,你可能会在五个字节后溢出)。使用实际位和字节更容易显示它。我将使用*32而不是*31算法(不太正确,但担心的更少,你会得到这个想法):

byte      hash is less than:
0000a000  00000000 00000000 00000000 0000a000
10000000  00000000 00000000 000000a0 10000000
b0000000  00000000 00000000 a0100000 b0000000
c0000000  00000000 00a01000 00b00000 c0000000
d0000000  0000a010 0000b000 00c00000 d0000000
anything  OVERFLOW!

如上所述,通过将所有内容声明为无符号整数,可以改善(相当差的)散列算法的可预测行为;我还建议初始化哈希值(并且一个非零值可能是一个好主意),而不是假设编译器将其设置为零(我不是100%确定定义的行为)。最后,如果您想知道溢出,并希望得到警告,我会修改代码如下:

for(p = s; *p; p++) {
    if((hashval > 0xFFFFFFFF/31) || (*p>>1 + 31 * (hashval>>1)) > 0x7FFFFFFF) {
        printf("hash is about to overflow at character %c\n", *p);
    }
    hashval = *p + 31 * hashval;
}