【问题标题】:A hash function like from K&R bookK&R 书中的散列函数
【发布时间】:2013-02-15 03:07:10
【问题描述】:

考虑这个函数:

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

如何测量s 中有多少字节会返回错误结果,例如溢出? 我在 32 位平台上。

【问题讨论】:

  • 不是hash点溢出没关系吗? “错误结果”是什么意思?

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


【解决方案1】:

如果你把它改为阅读

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 2n(其中 nunsigned 的宽度(以位为单位)。我还修复了未初始化变量的使用,并制作了sp const,这可能会改进优化和/或捕获函数体中的错误。

(我现在不记得确切的算术转换规则;一开始可能不可能。但是,以这种方式编写它显然不可能。)

顺便说一句,现在已知有更好的哈希函数:如果您没有充分的理由这样做,我建议使用SipHash

【讨论】:

  • 根据定义char 不是unsigned
  • @Floris 哦,要是这样就好了!但不是。实际上 实现定义 无论它是否有符号,并且对于实现而言,将其定义为有符号比无符号更常见(因为这提供了与其他整数类型的一致性)。
  • 我的立场是正确的。谢谢。这就是我们互相学习的方式。
【解决方案2】:

一些想法:

首先,哈希函数中会出现溢出。

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

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

【讨论】:

  • 不错的分析。感谢您注意到未初始化的变量,我已经在我的回答中解决了这个问题(这是严重的 undefined 行为,“编译器可能让恶魔飞出你的鼻子“ 种类)。不过,我认为零是这个粗略的哈希函数的正确初始值设定项。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-08-20
  • 2018-03-09
  • 2013-04-23
  • 2016-08-03
  • 2018-05-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多