散列函数的问题 - C

Posted

技术标签:

【中文标题】散列函数的问题 - C【英文标题】:Problem with hashing function - C 【发布时间】:2010-10-23 09:39:59 【问题描述】:

我正在使用 K&R 书中提供的以下散列函数。

#define HASHSIZE 101
unsigned hash(char *s)

    unsigned hashval;
    for (hashval = 0; *s != '\0'; s++)
        hashval = *s + 31 * hashval;
    return hashval % HASHSIZE;

在我的项目中,我打开了更多警告(警告也被视为错误),上面的代码将无法编译。

error: conversion to ‘unsigned int’ from ‘char’ may change the sign of the result

如果我对hashval 进行签名,我会得到负哈希值。我想知道如何解决这个问题。

有什么帮助吗?

【问题讨论】:

【参考方案1】:

您的编译器正在接受并警告您的是,您正在隐式更改对存储在s 指向的区域中的字节的解释。函数原型将s 指定为指向char 的指针,默认情况下,在您的设置中,chars 似乎已签名。但是,要使 has 算术正确,您只需要使用无符号值。所以问题是:编译器应该如何处理通过s 指向的实际上具有负值的值?

让我们快速转移一下注意力,以确保我们了解我们可能正在考虑的价值。 signed char 的可能值包括 CHAR_MINCHAR_MAX。 (这些值可以在 limits.h 中找到。)unsigned char 的可能值包括 0UCHAR_MAX。那么问题就变成了:我们如何在0UCHAR_MAX 的范围内表示从CHAR_MINCHAR_MAX 的可能值范围?

一种简单的方法是让编译器为您执行此转换:它只是使用环绕算法来确保该值在限制范围内:它会自动将UCHAR_MAX + 1 相加足够的次数以获得一个在限制范围内的值范围 0UCHAR_MAX但是,它的实际值可能取决于您正在使用的编译器。正是这种不可移植性的可能性隐藏在您的编译器警告背后。

好的,那我们在哪里呢?好吧,如果您准备对这种方法将产生的假设可移植性问题负责,您可以告诉编译器您很高兴它使用标准规则进行转换。您可以使用 cast 来做到这一点:

hashval = ((unsigned char) *s) + 31 * hashval;

这种方法将抑制警告并确保您的算术全部完成为无符号,这是您想要的这种 has 函数。但是,您需要注意,其他系统上的相同代码可能给出不同的哈希结果。

另一种方法是使用 ANSI C 标准指定指针可以有效地转换为类型 unsigned char * 以访问被指向数据的底层字节结构这一事实。 (我现在手头没有我的标准副本,或者我会给你一个参考。)这将允许你推广这种方法来生成一个函数,该函数为你提供任何数据值的哈希值类型。 (但是,要做到这一点,您必须考虑如何知道传入的数据的大小。)这可能类似于:

unsigned hash(void *s, size_t n) 
  unsigned char *t = (unsigned char *) s;

  while (n--)
    hashval = (*(t++) + 31 * hashval) % HASHSIZE;

  return hashval;

我希望这能让您对正在发生的事情有所了解。

【讨论】:

此答案中的第二个解决方案是唯一正确的解决方案。在读取后将char 转换为unsigned char 会将对应于 0 和 -0 的字节值在非二进制补码实现上折叠在一起。 有趣。您是说您相信存在符合标准的实现,其中-00char 变量的不同值。我有点怀疑。 很棒的解释。非常感谢。【参考方案2】:

将函数签名中的s 更改为unsigned char *,或者在使用时直接转换(即(unsigned char *)s)。

【讨论】:

那是改变接口来修复实现。 @larsmans:如果 char 被明确签署或未签署,我会同意你的看法。不是。 @Eric Towers,这将是一个接口更改,因为将指定输入的未指定属性。 @larsmans:如果 char != unsigned char,这只是一个接口更改。是吗?你不能知道。 (C 有 3 种 char 类型。“char”绝对是另外两种之一,但它是由实现定义的。) @Eric Towers:这是一个界面变化,因为charsigned charunsigned char是三种不同的类型。 ***.com/questions/436513/…【参考方案3】:

我认为您可以安全地将您的 char 类型转换为 unsigned: (unsigned char)*s

【讨论】:

不,你不能。这是对非二进制补码实现的有损操作。您必须投射指针:*(unsigned char *)s @R:在这种特殊情况下,因为该值用于散列,所以没关系。此外,不是所有非二进制补码机器以及非 8 位机器都停留在 1970 年代(如果不是更早的话)吗? ;)

以上是关于散列函数的问题 - C的主要内容,如果未能解决你的问题,请参考以下文章

C 的最小散列函数?

C散列函数采用2个数字

信息安全C散列函数的应用及其安全性2016011992

接受整数散列键的整数散列函数是好的?

c_cpp 可逆整数散列函数

散列表的概念及其拉链法和常见的散列函数(C语言)