[英]Trying to get value from hash table in C returns some random value instead of value
我在 C 中使用 hash 表库,但似乎每当我在与输入键值对的位置不同的 function 中检索某个键的值时,我都会得到一些随机值而不是实际值。
如果我使用我放置的同一个 function 中的键从 hash 表中获取值,则一切正常。 但是,当我将键值对放入一个 function 的哈希表中,然后尝试在另一个 function 中检索它时,它每次都会吐出一个随机的新值而不是预期值。 我尝试了 3 个不同的库(GNU Hash 表、UTHash 和 Ben Hoyt 的实现,所有这些我都在下面链接),但它们都不能跨函数工作。 我认为这不是库的问题,而是我检索值的方式有问题。 我在下面附上了代码和 output 的截图,以及我用于 hash 表的当前代码。
ht.c:
#include "ht.h"
#include <assert.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
// Hash table entry (slot may be filled or empty).
typedef struct {
const char* key; // key is NULL if this slot is empty
void* value;
} ht_entry;
#define INITIAL_CAPACITY 16 // must not be zero
// Hash table structure: create with ht_create, free with ht_destroy.
typedef struct ht {
ht_entry* entries; // hash slots
size_t capacity; // size of _entries array
size_t length; // number of items in hash table
};
ht* ht_create(void) {
// Allocate space for hash table struct.
ht* table = malloc(sizeof(ht));
if (table == NULL) {
return NULL;
}
table->length = 0;
table->capacity = INITIAL_CAPACITY;
// Allocate (zero'd) space for entry buckets.
table->entries = calloc(table->capacity, sizeof(ht_entry));
if (table->entries == NULL) {
free(table); // error, free table before we return!
return NULL;
}
return table;
}
void ht_destroy(ht* table) {
// First free allocated keys.
for (size_t i = 0; i < table->capacity; i++) {
if (table->entries[i].key != NULL) {
free((void*)table->entries[i].key);
}
}
// Then free entries array and table itself.
free(table->entries);
free(table);
}
#define FNV_OFFSET 14695981039346656037UL
#define FNV_PRIME 1099511628211UL
// Return 64-bit FNV-1a hash for key (NUL-terminated). See description:
// https://en.wikipedia.org/wiki/Fowler–Noll–Vo_hash_function
static uint64_t hash_key(const char* key) {
uint64_t hash = FNV_OFFSET;
for (const char* p = key; *p; p++) {
hash ^= (uint64_t)(unsigned char)(*p);
hash *= FNV_PRIME;
}
return hash;
}
void* ht_get(ht* table, const char* key) {
// AND hash with capacity-1 to ensure it's within entries array.
uint64_t hash = hash_key(key);
size_t index = (size_t)(hash & (uint64_t)(table->capacity - 1));
// Loop till we find an empty entry.
while (table->entries[index].key != NULL) {
if (strcmp(key, table->entries[index].key) == 0) {
// Found key, return value.
return table->entries[index].value;
}
// Key wasn't in this slot, move to next (linear probing).
index++;
if (index >= table->capacity) {
// At end of entries array, wrap around.
index = 0;
}
}
return NULL;
}
// Internal function to set an entry (without expanding table).
static const char* ht_set_entry(ht_entry* entries, size_t capacity,
const char* key, void* value, size_t* plength) {
// AND hash with capacity-1 to ensure it's within entries array.
uint64_t hash = hash_key(key);
size_t index = (size_t)(hash & (uint64_t)(capacity - 1));
// Loop till we find an empty entry.
while (entries[index].key != NULL) {
if (strcmp(key, entries[index].key) == 0) {
// Found key (it already exists), update value.
entries[index].value = value;
return entries[index].key;
}
// Key wasn't in this slot, move to next (linear probing).
index++;
if (index >= capacity) {
// At end of entries array, wrap around.
index = 0;
}
}
// Didn't find key, allocate+copy if needed, then insert it.
if (plength != NULL) {
key = strdup(key);
if (key == NULL) {
return NULL;
}
(*plength)++;
}
entries[index].key = (char*)key;
entries[index].value = value;
return key;
}
// Expand hash table to twice its current size. Return true on success,
// false if out of memory.
static bool ht_expand(ht* table) {
// Allocate new entries array.
size_t new_capacity = table->capacity * 2;
if (new_capacity < table->capacity) {
return false; // overflow (capacity would be too big)
}
ht_entry* new_entries = calloc(new_capacity, sizeof(ht_entry));
if (new_entries == NULL) {
return false;
}
// Iterate entries, move all non-empty ones to new table's entries.
for (size_t i = 0; i < table->capacity; i++) {
ht_entry entry = table->entries[i];
if (entry.key != NULL) {
ht_set_entry(new_entries, new_capacity, entry.key,
entry.value, NULL);
}
}
// Free old entries array and update this table's details.
free(table->entries);
table->entries = new_entries;
table->capacity = new_capacity;
return true;
}
const char* ht_set(ht* table, const char* key, void* value) {
assert(value != NULL);
if (value == NULL) {
return NULL;
}
// If length will exceed half of current capacity, expand it.
if (table->length >= table->capacity / 2) {
if (!ht_expand(table)) {
return NULL;
}
}
// Set entry and update length.
return ht_set_entry(table->entries, table->capacity, key, value,
&table->length);
}
size_t ht_length(ht* table) {
return table->length;
}
hti ht_iterator(ht* table) {
hti it;
it._table = table;
it._index = 0;
return it;
}
bool ht_next(hti* it) {
// Loop till we've hit end of entries array.
ht* table = it->_table;
while (it->_index < table->capacity) {
size_t i = it->_index;
it->_index++;
if (table->entries[i].key != NULL) {
// Found next non-empty item, update iterator key and value.
ht_entry entry = table->entries[i];
it->key = entry.key;
it->value = entry.value;
return true;
}
}
return false;
}
ht.h:
// Simple hash table implemented in C.
#ifndef _HT_H
#define _HT_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
// Hash table structure: create with ht_create, free with ht_destroy.
typedef struct ht ht;
// Create hash table and return pointer to it, or NULL if out of memory.
ht* ht_create(void);
// Free memory allocated for hash table, including allocated keys.
void ht_destroy(ht* table);
// Get item with given key (NUL-terminated) from hash table. Return
// value (which was set with ht_set), or NULL if key not found.
void* ht_get(ht* table, const char* key);
// Set item with given key (NUL-terminated) to value (which must not
// be NULL). If not already present in table, key is copied to newly
// allocated memory (keys are freed automatically when ht_destroy is
// called). Return address of copied key, or NULL if out of memory.
const char* ht_set(ht* table, const char* key, void* value);
// Return number of items in hash table.
size_t ht_length(ht* table);
// Hash table iterator: create with ht_iterator, iterate with ht_next.
typedef struct {
const char* key; // current key
void* value; // current value
// Don't use these fields directly.
ht* _table; // reference to hash table being iterated
size_t _index; // current index into ht._entries
} hti;
// Return new hash table iterator (for use with ht_next).
hti ht_iterator(ht* table);
// Move iterator to next item in hash table, update iterator's key
// and value to current item, and return true. If there are no more
// items, return false. Don't call ht_set during iteration.
bool ht_next(hti* it);
#endif // _HT_H
GNU C: https://www.gnu.org/software/libc/manual/html_node/Hash-Search-Function.html
UTHash: https://troydhanson.github.io/uthash/
本霍伊特: https://benhoyt.com/writings/hash-table-in-c/
感谢您的任何帮助。
查看您的屏幕截图,我看到:
void *data = &i;
这是将存在于堆栈中的变量的地址存储在test_hashtable
function 中。当您将该指针存储在 hash 表中,然后 function 退出时,您不能依赖它指向的数据。 数据没了,memory可能有别的用途。
所以你遇到的是未定义的行为。 如果您希望存储一个 int 值,那么您需要为其分配 memory 或实际将其塞入指针(对于适合指针的任何数据类型,这是一种相当普遍的做法)。
这是将 integer 存储在实际指针中的示例:
// store
const char *key = "Gate";
int i = 10;
void *data;
memcpy(&data, &i, sizeof(i));
ht_set(hashtable, key, data);
// retrieve
void *result = ht_get(hashtable, key);
int value;
memcpy(&value, &result, sizeof(value));
但是,您会在这里注意到一个问题,即由于ht_get
的调用语义使用 NULL 值表示没有数据,因此您无法将 integer 0
存储在表中。 所以,除非你想改变你的接口来返回一个“found”指示符和数据,那么你唯一的选择就是实际分配:
// store
const char *key = "Gate";
int i = 10;
int *data = malloc(sizeof(i));
if (data) {
*data = i;
ht_set(hashtable, key, data);
}
// retrieve
void *result = ht_get(hashtable, key);
if (result) {
int value = *(int*)result;
}
// note: you'll need to `free` the data when you remove the key from your hash table.
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.