尝试从 C 中的哈希表获取值返回一些随机值而不是值
我在 C 中使用哈希表库,但似乎每当我在与输入键值对不同的函数中检索某个键的值时,我都会得到一些随机值而不是实际值。
如果我使用放置它的同一函数中的键从哈希表中获取值,则一切正常。但是,当我将键值对放入一个函数中的哈希表中,然后尝试在另一个函数中检索它时,它每次都会输出一个随机的新值,而不是预期值。我尝试了 3 个不同的库(GNU 哈希表、UTHash 和 Ben Hoyt 的实现,我在下面链接了所有这些库),但它们都不能跨函数工作。我认为这不是库的问题,而是我检索值的方式有问题。我在下面提供了代码和输出的屏幕截图,以及我用于哈希表的当前代码。
<图片src="https://piazza.com/redirect/s3?bucket=uploads&prefix=paste%2Fksuvav6qtdg2vn%2F057c31dc6d 2987a2b6e05b404919b78d6245bc581994196904154f8e6ff6b4cd%2FScreen_Shot_2022-03-29_at_2.40.37_AM.png" alt="Screenshot">
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/
感谢您的帮助。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
查看您的屏幕截图,我看到:
这是存储
test_hashtable
函数中堆栈中存在的变量的地址。当您将该指针存储在哈希表中,然后将功能退出时,您不能依靠指向的数据。数据消失了,内存可以用于其他东西。因此,您遇到的是未定义的行为。如果您想存储一个int值,则需要为其分配内存或实际将其塞入指针中(对于适合指针内部的任何数据类型来说,这是相当普遍的实践)。
这是将整数存储在实际指针内的示例:
但是,您会在这里注意到一个问题,这是由于
ht_get 使用空值指示没有数据。因此,除非您想更改界面以返回数据以及数据以及数据,否则您唯一的选择是实际分配:
Looking at your screenshot, I see:
This is storing the address of a variable that exists on the stack inside your
test_hashtable
function. When you store that pointer in your hash table and then the function exits, you cannot rely on the data it points at. The data is gone, and the memory may be used for something else.So what you've encountered is undefined behavior. If you wish to store an int value then you'll need to either allocate memory for it or actually jam it into the pointer (which is a fairly common practice for any data types that fit inside a pointer).
Here is an example of storing the integer inside the actual pointer:
However, you'll notice one problem here, which is that you cannot store the integer
0
in your table due to the calling semantics ofht_get
which uses a NULL value to indicate no data. So, unless you want to change your interface to return a "found" indicator along with the data, then your only option is to actually allocate: