【问题标题】:How to use pointers correctly in C如何在 C 中正确使用指针
【发布时间】:2019-11-22 21:40:55
【问题描述】:

我正在尝试将 (key, value) 对添加到哈希图中,但插入后无法访问这些值。

这个哈希表应该处理冲突,因为每当发生冲突时,我都会沿着每个哈希索引进行迭代。然后,当我到达该索引处的(键,值)对列表的末尾时,我将其插入。

本质上是一个基本的链表hashmap。

问题是,当我再次尝试访问该值时,我不断遇到分段错误(我的 showTable() 函数也失败了)。在这个测试中,我只是尝试在每个哈希索引处添加一些内容后访问每个哈希索引处的第一个(键,值)对。我可能正在做一些非常愚蠢的事情,但我看到了。

我还没有评论,但我希望代码是不言自明的。重要的是InsertKeyValuePair(),但我已经添加了所有内容,因为代码审查也将是有益的。

#include <stdlib.h>
#include <stdio.h>

typedef struct TVal KeyValue;

typedef struct TVal {
    char *value;
    char *key;
    KeyValue *next;
} KeyValue;

typedef KeyValue **HashTable;

int MAX_SIZE = 200;

int HashKey(char *Key, int Max);
void InsertKeyValuePair(char *key, char *value, int Index, HashTable table);
int insert(char *Key, char *value, HashTable table, int size);
void showTable(HashTable table, int size);

int HashKey(char *Key, int Max) {
    char c = *Key;
    int Hash = 0;
    int n = 1;

    while (c != 0) {
        Hash += n * ((int)c);
        c = *(Key + n);
        n++;
    }

    return Hash % MAX_SIZE;
}

void InsertKeyValuePair(char *key, char *value, int Index, HashTable table) {
    KeyValue *cursor = *(table + Index);
    while (cursor != NULL) {
        cursor = cursor->next;
    }
    cursor = malloc(sizeof(KeyValue));
    cursor->value = value;
    cursor->key = key;
    printf("insert <K,V>(%s,%s) HashIndex = %i\n", cursor->key, cursor->value, Index);

    //Trying to access value previously inserted
    KeyValue *cursor2 = *(table + Index);
    printf("<K,V>(%s,%s)\n", cursor2->key, cursor2->value);
}

int insert(char *Key, char *value, HashTable table, int size) {
    int Index = HashKey(Key, MAX_SIZE);
    InsertKeyValuePair(Key, value, Index, table);
    return size + 1;
}

void showTable(HashTable table, int size) {
    int i;
    for (i = 0; i < size; i++) {
        KeyValue *cursor = *(table + i);

        if (cursor == NULL) 
            continue;

        while (cursor != NULL) {
            printf("==============");
            printf("<K,V>(%s,%s)\n", cursor->key, cursor->value);
            cursor = cursor->next;
        }   
        printf("==============");
    }
}

int main() {
    HashTable HTbl = malloc(sizeof(HashTable) * MAX_SIZE);
    int size = 0;

    size = insert("yeuydfdan", "wesfg", HTbl, size);
    size = insert("ywere", "rdgg", HTbl, size);
    size = insert("ye4", "3244", HTbl, size);

    //showTable(HTbl, MAX_SIZE);
}

【问题讨论】:

  • 您的HTbl 最终成为指向KeyValue 的指针的指针。请解释你为什么这样设计。仔细检查您是否已为这三个级别中的每一个分配了内存。这会让你从这条赛道开始:ericlippert.com/2014/03/05/how-to-debug-small-programs
  • @Yunnosch: Htbl 只是一个指向KeyValue 的指针。
  • @alk 是它的类型(Hashtable,是指向指针的指针),但是它被赋值为malloc的结果,它是(或应该被使用的)作为指针中使用的类型sizeof(),即Hashtable,即三重指针。或者换一种说法,应该是Hashtable* HTbl= malloc(sizeof(HashTable)*MAX_SIZE)才有意义。
  • ht.c:49:5:运行时错误:“struct KeyValue”类型的空指针内的成员访问

标签: c pointers linked-list hashtable singly-linked-list


【解决方案1】:

您的代码中存在多个问题:

  • 哈希表未初始化为NULL,导致尝试取消引用其中包含的指针时出现分段错误。使用calloc() 进行分配可以解决此问题。
  • 将指针隐藏在 typedef 后面会令人困惑且容易出错。
  • main 中的分配应为HashTable HTbl = calloc(sizeof(*HTbl), MAX_SIZE);
  • InsertKeyValuePair 中的插入代码不会在末尾链接新对,也不会在哈希表桶列表的开头链接。
  • 建议使用unsigned 算法来计算哈希键以避免溢出问题。
  • 指针符号*(table + Index) 令人困惑。您应该改用数组表示法table[Index]
  • 哈希表的长度 (MAX_SIZE) 和哈希表中的条目数 (size) 之间似乎有些混淆。适当地重命名变量可以提高可读性。通过地址传递计数并返回成功指示符也可能更好。

这是一个更正的版本:

#include <stdlib.h>
#include <stdio.h>

typedef struct TVal KeyValue;

typedef struct TVal {
    const char *value;
    const char *key;
    KeyValue *next;
} KeyValue;

typedef KeyValue **HashTable;

static unsigned int HASH_SIZE = 200;

static unsigned int HashKey(const char *key);
static KeyValue *InsertKeyValuePair(const char *key, const char *value, int index, HashTable table);
static int insert(const char *Key, const char *value, HashTable table, int *countp);
static void showTable(HashTable table);

static unsigned int HashKey(const char *key) {
    unsigned int hash = 0;
    size_t n;

    for (n = 0; key[n] != 0; n++) {
        hash += n * (unsigned char)key[n];
    }
    return hash % HASH_SIZE;
}

static KeyValue *InsertKeyValuePair(const char *key, const char *value, int index, HashTable table) {
    KeyValue *cursor;
    cursor = malloc(sizeof(KeyValue));
    if (cursor != NULL) {
        KeyValue **cursorp = &table[index];
        while (*cursorp != NULL) {
            cursorp = &(*cursorp)->next;
        }
        cursor->value = value;
        cursor->key = key;
        cursor->next = NULL;
        *cursorp = cursor;
    }
    return cursor;
}

static int insert(const char *key, const char *value, HashTable table, int *countp) {
    int index = HashKey(key);
    if (InsertKeyValuePair(key, value, index, table)) {
        *countp += 1;
        return 1;
    }
    return 0;
}

static void showTable(HashTable table) {
    unsigned int i;
    for (i = 0; i < HASH_SIZE; i++) {
        KeyValue *cursor = table[i];

        if (cursor == NULL)
            continue;

        while (cursor != NULL) {
            printf("==============");
            printf("<K,V>(%s,%s)\n", cursor->key, cursor->value);
            cursor = cursor->next;
        }
        printf("==============\n");
    }
}

int main() {
    HashTable HTbl = calloc(sizeof(*HTbl), HASH_SIZE);
    int count = 0;

    insert("yeuydfdan", "wesfg", HTbl, &count);
    insert("ywere", "rdgg", HTbl, &count);
    insert("ye4", "3244", HTbl, &count);

    showTable(HTbl);
    return 0;
}

【讨论】:

  • 感谢您提供有用的反馈。
【解决方案2】:

此声明

HashTable HTbl = malloc(sizeof(HashTable)*MAX_SIZE);

不正确,而且分配的内存未初始化。应该有

HashTable HTbl = calloc( MAX_SIZE, sizeof( KeyValue * ) );

或喜欢

HashTable HTbl = calloc( MAX_SIZE, sizeof( *HTbl ) );

表中的索引应计算为某个无符号整数。否则一般你会得到一个负索引。

在函数HashKey参数Max没有使用。

在函数InsertKeyValuePair 中更改了局部变量cursor,而不是数据成员cursor-&gt;next*(table+Index)

函数showTable 中的循环应在循环条件中使用MAX_SIZE 而不是size。也就是说,您必须将 MAX_SIZE 的值而不是 size 的值作为参数传递。

这是一个演示程序,展示了如何更新程序。

#include <stdio.h>
#include <stdlib.h>

typedef struct TVal KeyValue;

typedef struct TVal 
{
    const char *value;
    const char *key;
    KeyValue *next;
} KeyValue;


typedef KeyValue **HashTable;

const size_t MAX_SIZE = 200;

static size_t HashKey( const char *key, size_t max_slots ) 
{
    size_t Hash = 0;

    for ( size_t i = 0; key[i]; i++ ) Hash += ( i + 1 ) * ( unsigned char )key[i];

    return Hash % max_slots;
}

static int InsertKeyValuePair( HashTable table, const char *key, const char *value, size_t index ) 
{
    KeyValue **cursor = &table[index];

    while ( *cursor != NULL ) cursor = &( *cursor )->next;

    *cursor = malloc( sizeof( KeyValue ) );

    int success = *cursor != NULL;

    if ( success )
    {
        ( *cursor )->value = value;
        ( *cursor )->key = key;
        ( *cursor )->next = NULL;
    }

    return success;
}

int insert( HashTable table, const char *key, const char *value, size_t *size ) 
{
    size_t index = HashKey( key, MAX_SIZE );

    int success = InsertKeyValuePair( table, key, value, index );

    if ( success ) ++*size;

    return success;
}

void showTable( HashTable table, size_t size )
{
    for ( size_t i = 0; i < size; i++ )
    {
        KeyValue *cursor = table[i];

        if ( cursor != NULL )
        {
            do
            {
                puts( "==============" );
                printf( "<K,V>(%s, %s)\n", cursor->key, cursor->value );
                cursor = cursor->next;
            } while ( cursor != NULL );

            puts( "==============\n" );
        }
    }        
}

int main( void )
{
    HashTable HTbl = calloc( MAX_SIZE, sizeof( *HTbl ) );

    size_t size = 0;

    insert( HTbl, "yeuydfdan", "wesfg", &size );
    insert( HTbl, "ywere", "rdgg", &size );
    insert( HTbl, "ye4", "3244", &size );

    showTable( HTbl, MAX_SIZE );
}

程序输出是

==============
<K,V>(ywere, rdgg)
==============

==============
<K,V>(ye4, 3244)
==============

==============
<K,V>(yeuydfdan, wesfg)
==============

当然,您应该添加一些其他功能,例如删除带有节点的表的功能。

如果每个节点都为键和值分配内存并将传递的参数复制到那里会更好。否则,该表通常只能处理字符串文字,因为它们具有静态存储持续时间。

如果您将重写表的实现,使其复制表节点中的键和值,那么结构应定义为

typedef struct TVal KeyValue;

typedef struct TVal 
{
    char *value;
    const char *key;
    KeyValue *next;
} KeyValue;

在任何情况下都不应该更改密钥,并且应该使用限定符 const 声明。

【讨论】:

  • “对于初学者来说,这个结构声明是不正确的” - 不,不是。你错过了第一个 typedef。
  • @klutt 哦,是的。当我写这句话时,我考虑了我的演示程序。:)
  • 最好使用sizeof(*HTbl) 而不是sizeof( KeyValue * )
  • 感谢大家提供有用的反馈。
猜你喜欢
  • 2019-11-10
  • 1970-01-01
  • 1970-01-01
  • 2021-08-25
  • 2012-02-20
  • 2014-03-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多