【问题标题】:C hsearch finds values not entered beforechsearch 查找以前未输入的值
【发布时间】:2016-01-12 20:13:27
【问题描述】:

这是this question 的后续问题。

由于我solved一部分,感觉剩下的问题和第一个无关,所以决定把这两部分分成两个问题。

我已经按照建议的here 使用 POSIX hcreate/hsearch 实现了一个关联数组。

为了完整起见,这里是除上一个问题中的main() 函数之外的所有代码:

#include <inttypes.h> /* intptr_t             */
#include <search.h>   /* hcreate(), hsearch() */
#include <stdio.h>    /* perror()             */
#include <stdlib.h>   /* exit()               */
#include <string.h>   /* strcpy()             */

void exit_with_error(const char* error_message){
  perror(error_message);
  exit(EXIT_FAILURE);
}
int fetch(const char* key, intptr_t* value){
  ENTRY e,*p;
  e.key=(char*)key;
  p=hsearch(e, FIND);
  if(!p) return 0;
  *value=(intptr_t)p->data;
  return 1;
}

void store(const char *key, intptr_t value){
  ENTRY e,*p;
  e.key=(char*)key;
  p = hsearch(e, ENTER);
  if(!p) exit_with_error("hash full");
  p->data = (void *)value;
}

这里是一个稍微扩展的main()函数:

int main(){
  char a[4]="foo";
  char b[4]="bar";
  char c[4]="baz";
  char t[4]="";
  char y='\0';
  const char l[6]={'a','b','f','o','r','z'};
  intptr_t x=NULL;
  size_t i=0,j=0;

  if(!hcreate(50)) exit_with_error("no hash");

  strcpy(t,b);
  store(t,0);
  if(fetch(t,&x)) printf("stored %s-->%d\n",t,(int)x);
  else printf("%s not stored\n",t);

  for(i=0;i<3;++i){
    y=t[i];
    for(j=0;j<6;++j){
      if(l[j]==y) continue;
      t[i]=l[j];
      if(fetch(t,&x)) store(t,-1);
      else store(t,1);
      if(fetch(t,&x)) printf("stored %s-->%d\n",t,(int)x);
      else printf("%s not stored\n",t);
    }   
    t[i]=y;
  }

  strcpy(t,a); if(fetch(t,&x)) printf("read %s-->%d\n",t,(int)x); else printf("%s not found\n",t);
  strcpy(t,b); if(fetch(t,&x)) printf("read %s-->%d\n",t,(int)x); else printf("%s not found\n",t);
  strcpy(t,c); if(fetch(t,&x)) printf("read %s-->%d\n",t,(int)x); else printf("%s not found\n",t);

  exit(EXIT_SUCCESS);
}

如您所见,我将字符串bar0 关联起来。 然后,我将所有与bar 完全不同的单词与1 相关联,如果它们之前没有添加,或者-1 否则。 最后,我查找 foobarbaz 得到预期值:

stored bar-->0
stored aar-->1
stored far-->1
stored oar-->1
stored rar-->1
stored zar-->1
stored bbr-->-1
stored bfr-->1
stored bor-->1
stored brr-->1
stored bzr-->1
stored baa-->1
stored bab-->1
stored baf-->1
stored bao-->1
stored baz-->1
foo not found
read bar-->0
read baz-->1

现在我稍微修改一下将foobarbaz替换为CCCTTCTTATCGCCCTTCATTGCG的函数:

int main(){
  char a[13]="CCCTTCTTATCG"
            /*|||||| |  ||*/
  char b[13]="CCCTTCATTGCG";
  char t[13]="";
  char y='\0';
  const char l[4]={'A','C','G','T'};
  intptr_t x=NULL;
  size_t i=0,j=0;

  if(!hcreate(150)) exit_with_error("no hash");

  strcpy(t,a);
  store(t,0); 
  if(fetch(t,&x)) printf("stored %s-->%d\n",t,(int)x);
  else printf("%s not stored\n",t);

  for(i=0;i<12;++i){
    y=t[i];
    for(j=0;j<4;++j){
      if(l[j]==y) continue;
      t[i]=l[j];
      if(fetch(t,&x)) store(t,-1);
      else store(t,1);
      if(fetch(t,&x)) printf("stored %s-->%d\n",t,(int)x);
      else printf("%s not stored\n",t);
    }   
    t[i]=y;
  }

  strcpy(t,a); if(fetch(t,&x)) printf("read %s-->%d\n",t,(int)x); else printf("%s not found\n",t);
  strcpy(t,b); if(fetch(t,&x)) printf("read %s-->%d\n",t,(int)x); else printf("%s not found\n",t);

  exit(EXIT_SUCCESS);
}

为了清楚起见,这里是对应的diff

29,32c29,32
<   char a[4]="foo";
<   char b[4]="bar";
<   char c[4]="baz";
<   char t[4]="";
---
>   char a[13]="CCCTTCTTATCG";
>             /*|||||| |  ||*/
>   char b[13]="CCCTTCATTGCG";
>   char t[13]="";
34c34
<   const char l[6]={'a','b','f','o','r','z'};
---
>   const char l[4]={'A','C','G','T'};
38c38
<   if(!hcreate(50)) exit_with_error("no hash");
---
>   if(!hcreate(150)) exit_with_error("no hash");
40c40
<   strcpy(t,b);
---
>   strcpy(t,a);
45c45
<   for(i=0;i<3;++i){
---
>   for(i=0;i<12;++i){
47c47
<     for(j=0;j<6;++j){
---
>     for(j=0;j<4;++j){
60d59
<   strcpy(t,c); if(fetch(t,&x)) printf("read %s-->%d\n",t,(int)x); else printf("%s not found\n",t);

如您所见,CCCTTCATTGCG 有 3 个来自CCCTTCTTATCG 的编辑(如来自barfoo),因此不应在哈希表中找到。

但是,这是相应的输出:

stored CCCTTCTTATCG-->0
stored ACCTTCTTATCG-->1
stored GCCTTCTTATCG-->1
stored TCCTTCTTATCG-->1
stored CACTTCTTATCG-->1
stored CGCTTCTTATCG-->1
stored CTCTTCTTATCG-->1
stored CCATTCTTATCG-->1
stored CCGTTCTTATCG-->1
stored CCTTTCTTATCG-->1
stored CCCATCTTATCG-->1
stored CCCCTCTTATCG-->1
stored CCCGTCTTATCG-->1
stored CCCTACTTATCG-->1
stored CCCTCCTTATCG-->1
stored CCCTGCTTATCG-->1
stored CCCTTATTATCG-->1
stored CCCTTGTTATCG-->1
stored CCCTTTTTATCG-->1
stored CCCTTCATATCG-->1
stored CCCTTCCTATCG-->1
stored CCCTTCGTATCG-->1
stored CCCTTCTAATCG-->1
stored CCCTTCTCATCG-->1
stored CCCTTCTGATCG-->1
stored CCCTTCTTCTCG-->-1
stored CCCTTCTTGTCG-->-1
stored CCCTTCTTTTCG-->-1
stored CCCTTCTTAACG-->-1
stored CCCTTCTTACCG-->-1
stored CCCTTCTTAGCG-->-1
stored CCCTTCTTATAG-->-1
stored CCCTTCTTATGG-->-1
stored CCCTTCTTATTG-->-1
stored CCCTTCTTATCA-->-1
stored CCCTTCTTATCC-->-1
stored CCCTTCTTATCT-->-1
read CCCTTCTTATCG-->-1
read CCCTTCATTGCG-->1

如您所见,CCCTTCTTATCG-1 相关联,即使它从未被存储。

对于在存储中分配了-1 的所有字符串也是如此,因为它们在即将插入时已经在哈希中。

上述较小示例中的bbr 也是如此。

为什么hsearch 会为这些键返回1,即使它们从未被插入?

【问题讨论】:

  • 回到家,问题是关于hsearch(),这里没有发布该功能?
  • hcreate()、hdestroy() 和 hsearch() 函数首次出现在 AT&T System V UNIX 中。至少手册页是这么说的。
  • 我的猜测:hsearch 将提供的密钥字符串保留在哈希表中而不进行复制,因此您不能只是重复使用相同的缓冲区来构建密钥并插入它们。在使用缓冲区进行存储后修改缓冲区会导致哈希表在内部变得不一致。
  • 对于 POSIX,它是 int main(),而不是 void main()
  • 发布的代码包含几个“神奇”数字。例如:3、4、6、50。“神奇”数字使代码更难理解、调试和维护。建议使用#define 或枚举为这些数字赋予有意义的名称,然后在整个代码中使用这些有意义的名称。

标签: c posix associative-array


【解决方案1】:

手册页指出key 应该是一个用malloc 分配的字符串。以下是手册页中的一些相关引用

hdestroy() 函数为每个比较键调用 free(3) 搜索表而不是与键关联的数据项。

必须分配比较键(作为 item.key 传递给 hsearch()) 如果 action 是 ENTER 并且调用了 hdestroy() 则使用 malloc(3)。

所以调用hsearch 和动作ENTER 会在哈希中存储一个指针,并且该指针应该指向一个稍后可以释放的字符串。在您的代码中,您只有一个字符串缓冲区,每次向哈希表添加一个条目时,您都会传递相同的指针(指向您的字符串缓冲区)。这会使哈希表变得一团糟。

解决问题很简单。在store 函数中,在将条目添加到表之前,使用strdup 复制key。换句话说,替换

e.key=(char*)key;

e.key=strdup(key);

【讨论】:

  • @Barmar 因为哈希表不会复制字符串。它只存储指针。如果所有指针都指向同一个缓冲区,则哈希表中的所有字符串都是当前缓冲区中的任何内容。换句话说,在使用缓冲区作为 key 之后,不允许对缓冲区的内容进行更改。
  • 有趣的是,该文本没有出现在 POSIX 规范 pubs.opengroup.org/onlinepubs/9699919799 但是,示例代码注意不要对每个键使用相同的指针。它没有使用malloc,而是有一个很长的char 数组,并将每个键读入数组中的不同位置。
  • 似乎文档应该在某处说存储密钥字符串后不应修改它们。
  • @Barmar 是的,文档应该对此非常清楚。这是隐含的(如果free 的能力被假定为表明所有权),但应该明确说明。
  • 这在我的文档中真的不清楚。不过我说得有道理。最重要的是它有效。
猜你喜欢
  • 2013-12-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-24
相关资源
最近更新 更多