0

下面是 C# 中 GetHashCode32 方法的代码:

public static class StringHelper
{
    public static unsafe int GetHashCode32(this string s)
    {
        fixed (char* str = s.ToCharArray())
        {
            char* chPtr = str;
            int num = 0x15051505;
            int num2 = num;
            int* numPtr = (int*)chPtr;
            for (int i = s.Length; i > 0; i -= 4)
            {
                num = ( ( (num << 5) + num) + (num >> 0x1b)) ^ numPtr[0];
                if (i <= 2)
                {
                    break;
                }
                num2 = ( ( (num2 << 5) + num2) + (num2 >> 0x1b)) ^ numPtr[1];
                numPtr += 2;
            }
            return (num + (num2 * 0x5d588b65));
        }
    }
}

我用 C 语言重写了这个方法,如下所示:

#include <stdio.h> 
#include <string.h> 

int main()           
{                  
    char    str[320+1];      
    memset(str, 0, sizeof(str));
    int     i;              
    scanf("%s", str);

    char *chPtr = str;
    int num = 0x15051505;
    int num2 = num;
    int *numPtr = (int*)chPtr;

    for (i = strlen(str); i > 0; i -= 4) {
        num = (((num << 5) + num) + (num >> 0x1b)) ^ numPtr[0];
        if (i <= 2)
        {       
            break;
        }                 
        num2 = ( ( (num2 << 5) + num2) + (num2 >> 0x1b)) ^ numPtr[1];
        numPtr += 2;
    } 
    printf("hash code: %d\n", num + (num2 * 0x5d588b65));
    return 0;
}

c代码在-m32模式下编译。 但这两个功能有不同的输出

当输入为“354707043566597”时

我的 c 代码输出是 637077169,而在 GetHashCode32() 中它应该是 -1744455423。

GetHashCode32 是 C# 的库方法。所以是对的。但我不知道我的 C 代码有什么问题。谢谢!

4

3 回答 3

1

我可以给你一些可能不同的原因:

首先,0x15051505==0b10101000001010001010100000101如果算上它,它的长度是 29 位。根据 C 标准,将其左移 5 会产生未定义的行为,假设sizeof(int) <= 4.

其次,这一行:

int *numPtr = (int*)chPtr;

可能在 C 版本中搞砸了(我不知道 C# 如何处理指针,所以我不能在那里说)。当您这样做numPtr += 2;时,将其视为 achar*与 an完全不同int*(一个会将其移动 2 个字节,另一个将移动2 * sizeof(int)字节。因此,您实际上是在取消引用超出字符串范围的内存(假设sizeof(int) == 4),再次导致未定义的行为。

于 2013-09-12T04:24:24.520 回答
0

C char 是 1 个字节,它是有符号的,C# char 是 2 个字节,在 C# 中,第一个字节是无符号的。

这意味着如果您在内存中有一个字符串并使用一个 4 字节的 int 指针来访问该内存,您会将 C 中的 4 个字符放入一个 int,但在 C# 中只有 2 个字符放入一个 int。所以这永远不会产生相同的结果。

如何解决:在 C 中使用数据类型 wchar_t,它应该更接近 C# 的 char。您可以使用wscanf直接从控制台读取wchar_t缓冲区。

于 2019-01-12T12:41:23.670 回答
-1

问题是原始算法从内存中读取 UTF-16 (Unicode) 字符串,一次读取两个。

预期数据用零填充:

0x00350033 (numPtr[0]) lp0: "35"
0x00370034 (numPtr[1])      "47"
0x00370030 (numPtr[0]) lp1: "07"
0x00340030 (numPtr[1])      "04"
0x00350033 (numPtr[0]) lp2: "35"
0x00360036 (numPtr[1])      "66"
0x00390035 (numPtr[0]) lp3: "59"
0x00000037 (numPtr[1])      "7"

C 中提供的数据不同(并且不正确):

0x37343533 (numPtr[0]) lp0: "3547"
0x34303730 (numPtr[1])      "0704"
0x36363533 (numPtr[0]) lp1: "3566"
0x00373935 (numPtr[1])      "597"
0x00000000 (numPtr[0]) lp2: ""
0x00000000 (numPtr[1])      ""
0x00000000 (numPtr[0]) lp3: ""
0x00000000 (numPtr[1])      ""

一种快速的解决方案是强制numPtr转换为char(而不是int),并手动构造预期的格式以保持哈希兼容性。请记住,本机 GetHashCode 甚至不是完全确定的——Visual Studio 和 Mono 对相同的输入产生不同的结果。

于 2019-01-12T12:02:13.843 回答