在C中重写GetHashCode32(string s)方法

时间:2013-09-12 03:10:09

标签: c# c hashcode

以下是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代码有什么问题。 谢谢!

3 个答案:

答案 0 :(得分:1)

我可以告诉你为什么它可能会有所不同:

首先,0x15051505 == 0b10101000001010001010100000101,如果算一算,则为29位长。如果将sizeof(int) <= 4假设为int *numPtr = (int*)chPtr; ,则按5的左移将产生未定义的行为。

其次,这一行:

numPtr += 2;

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

答案 1 :(得分:0)

C char是1个字节,并且是带符号的,C#char是2个字节,而在C#中,第一个字节是无符号的。

这意味着,如果您在内存中有一个字符串并使用4字节的int poitner访问该内存,则在C中将4个字符转换为一个int,而在C#中将2个字符转换为一个int。因此,这永远不会产生相同的结果。

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

答案 2 :(得分:-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对相同的输入产生不同的结果。