#include <stdio.h>
unsigned int getHashNum(const char *str,unsigned int len,unsigned int maxPrime)
{
register unsigned int sum = 0;
register unsigned int h = 0;
register unsigned short *p = (unsigned short *)str;
register unsigned short *s = (unsigned short *)str;
printf("str:%s,len:%d,h:%d,maxPrime:%d\n",p,len,h,maxPrime);
while(p - s < len)
{
register unsigned short a = *(p++) * (p-s);
printf("a:%d\n",a);
sum += sum ^ a;
h += a;
}
printf("sum:%d\n",sum);
return ((sum << 16) | h) % maxPrime;
}
上面那段代码在主进程和子进程中都调用,传入的参数是一样的,但是返回值不一样,谁能看出来点端倪?
主进程输出:
str:key,len:3,h:0,maxPrime:60
a:0
a:121
a:49900
sum:49934
子进程输出:
str:key,len:3,h:0,maxPrime:60
a:0
a:121
a:29896
sum:29994
我大体找到原因了,问题中的代码是我从网上找到的,确实是为了计算hash值。就像 @米克 说的那样,这里是由于指针出现了越界。函数的入参const char *str
,在函数内部被转化成了unsigned short *
,即这句代码register unsigned short *p = (unsigned short *)str;
。c语言中char
是一个字节,short是两个字节,指针str
是指向char
的,那么如果执行运算str+1
,得到的结果应该是str的内存地址加一;但是换成表达式p+1
,那么就应该是p的内存地址加二(而不是 @米克 兄说的每次移动4个位置的内存地址距离)。也就是说,在代码中while
循环中,p
的地址每次都是加二的,所以*p
的内容第一次是k
的assic码,第二次是y
的assic码,第三次就越界了,如果是在同一个进程中,多次调用越界得到的内容很有可能是相同的,但是如果换了一个进程,那么越界得到的内容就极有可能是不同的。所以就出现了之前遇到的问题。
将while循环稍微修改一下,增加对p内存地址的打印:
while(p - s < len)
{
register unsigned short a =0;
printf("value:%d[%c][%d],offset:%d\n",*p,*p,p,p-s);
a= *(p++) * (p-s);
printf("a:%d\n",a);
sum += sum ^ a;
h += a;
}
主进程中打出来的是这样的:
str:key,len:3,h:0,maxPrime:60
value:25963[k][17598552],offset:0
a:0
value:121[y][17598554],offset:1
a:121
value:24950[v][17598556],offset:2
a:49900
sum:49934
子进程中打印出来的是这样的:
str:key,len:3,h:0,maxPrime:60
value:25963[k][13954256],offset:0
a:0
value:121[y][13954258],offset:1
a:121
value:14948[d][13954260],offset:2
a:29896
sum:29994
可以很清晰的看到内存越界,并且p
指针是每次移动两字节的。
解决方法也很简单,将p
改为使用char
类型指针:
unsigned int getHashNum(const char *str,unsigned int len,unsigned int maxPrime)
{
register unsigned int sum = 0;
register unsigned int h = 0;
register unsigned char *p = (unsigned char *)str;
register unsigned char *s = (unsigned char *)str;
printf("str:%s,len:%d,h:%d,maxPrime:%d\n",p,len,h,maxPrime);
while(p - s < len)
{
register unsigned short a =0;
printf("value:%d[%c][%d],offset:%d\n",*p,*p,p,p-s);
a= (unsigned short)*(p++) * (p-s);
printf("a:%d\n",a);
sum += sum ^ a;
h += a;
}
printf("sum:%d\n",sum);
return ((sum << 16) | h) % maxPrime;
}
如果len>=strlen(str)而又是单数的话,就有可能出这个问题
最有可能的情况就是对 str
的操作越界了,只有这样才有可能操作到不受控的数据。
检查下 while 条件和 *(p++) * (p-s)
这个表达式的结果,我觉得像这样还好控制些
short p = 0; // offset
short s = 0; // start, 可以省略了
while (p < length) {
short a = str[p++] * p
}
补充
我对 *(p++) * (p-s)
的运算顺序不是很清楚,可以尝试一下 (*(p++)) * (p-s)
(不知道你是不是这个意思)。
看运行的结果,
- 第 1 次取第1个字符,
p-s
的值为0
,得到0
- 第 2 次取第2个字符,
p-s
的值为1
,应该得到'e'
的 ASCII 码101
,但实际得到的是'y'
的 ASCII 码121
,所以这里肯定计算错了,有可能是按* ((p++) * (p-s))
算的 - 是按
* ((p++) * (p-s))
算的,第 3 次越界越得有点远……
对 str 的操作越界了,unsigned short p, p++ 实际往后对(char *)str移动了sizeof(unsigned short) = 4的距离,如果str长度为3,第一次循环就会跑出str的界, 从而对p取值就说不定了。
看出你的本意是按blocks对str求hash值(block大小是一个short),但是在实际指针操作过程有点问题。简单起见,你可以按单字节计算,将p,s指针类型改成unsigned char×。
如果想要实现按blocks计算,可以参考murmur hash方法。
···
unsigned int murmur3_32(const char *key, unsigned int len, unsigned int seed)
{
static const unsigned int c1 = 0xcc9e2d51;
static const unsigned int c2 = 0x1b873593;
static const unsigned int r1 = 15;
static const unsigned int r2 = 13;
static const unsigned int m = 5;
static const unsigned int n = 0xe6546b64;
unsigned int hash = seed;
const int nblocks = len / 4;
const unsigned int *blocks = (const unsigned int *) key;
int i;
for (i = 0; i < nblocks; i++) {
unsigned int k = blocks[i];
k *= c1;
k = (k << r1) | (k >> (32 - r1));
k *= c2;
hash ^= k;
hash = ((hash << r2) | (hash >> (32 - r2))) * m + n;
}
const char *tail = (const char *) (key + nblocks * 4);
unsigned int k1 = 0;
switch (len & 3) {
case 3:
k1 ^= tail[2] << 16;
case 2:
k1 ^= tail[1] << 8;
case 1:
k1 ^= tail[0];
k1 *= c1;
k1 = (k1 << r1) | (k1 >> (32 - r1));
k1 *= c2;
hash ^= k1;
}
hash ^= len;
hash ^= (hash >> 16);
hash *= 0x85ebca6b;
hash ^= (hash >> 13);
hash *= 0xc2b2ae35;
hash ^= (hash >> 16);
return hash;
}