将NULL指针强制转换为其他类型会怎样?为何两个断言一成功一失败?
问题
我有如下代码片段:
assert_ptr_equals(get_data(hm,key_three),NULL); assert_true((int*)get_data(hm,key_three)==NULL);
其中get_data函数返回void*指针,第一个断言成立,但第二个断言失败,请问这是什么原因?
最小可复现代码
#include<stdlib.h> #include<stddef.h> #include <string.h> #include <stdlib.h> #include <stdio.h> #include <sys/wait.h> #include <sys/types.h> #include <unistd.h> #define ASSERTION_FAILURE_EXITCODE 47 #define assert_true(x) assert_that(x) #define assert_ptr_equals(x, y) assert_ptr_equals_internal(__FILE__, __extension__ __FUNCTION__, __LINE__, #x, x, #y, y) #define assert_that(x) assert_that_internal(__FILE__, __extension__ __FUNCTION__, __LINE__, #x, x) void assert_ptr_equals_internal(const char *file_name, const char *function_name, int line_number, const char *xname, void *x, const char *yname, void *y) { if (x != y) { fprintf(stderr, "%s:%s:%d: Expected <%s>:%p to be equal to <%s>:%p.\n", file_name, function_name, line_number, xname, x, yname, y); exit(ASSERTION_FAILURE_EXITCODE); } } void assert_that_internal(const char *file_name, const char *function_name, int line_number, const char *condition_name, int condition) { if (!condition) { fprintf(stderr, "%s:%s:%d: Expected '%s' to hold.\n", file_name, function_name, line_number, condition_name); exit(ASSERTION_FAILURE_EXITCODE); } } typedef void * Data; typedef Data (* ResolveCollisionCallback)(Data oldData, Data newData); typedef void (* CallbackIterate)(char * key, Data data); typedef void (* DestroyDataCallback)(Data data); typedef struct { Data * data; //Data pointer to the data char * key; //char pointer to the string key } HashMapItem; typedef struct hashmap { HashMapItem ** items; //items of the hashmaps size_t size; //size of the hashmaps int count; //how many elements are in the hashmap } HashMap; unsigned int hash(const char * key){ //sum of the charaters unsigned int sum = 0; for(int i=0; key[i] != '\0'; i++){ sum += key[i]; } return sum; } HashMap * create_hashmap(size_t key_space){ if(key_space == 0) return NULL; HashMap * hm = (HashMap *) malloc(sizeof(HashMap)); //allocate memory to store hashmap hm->items = (HashMapItem **) calloc(key_space, sizeof(HashMapItem**)); //allocate memory to store every item inside the map, null it hm->size = key_space; //set sitze of hashmap hm->count = 0; //empty at the begining return hm; } void insert_data(HashMap* hm, const char * key, const Data data, const ResolveCollisionCallback resolve_collison){ if(key == NULL || hm == NULL || data == NULL){ return; } //index where to put data unsigned int index = hash(key) % hm->size; if((hm->items)[index] != NULL){ (hm->items)[index]->data = (Data *)malloc(sizeof(Data *)); //allocate memory to store the address if(resolve_collison!=NULL) *(hm->items)[index]->data = resolve_collison((hm->items)[index]->data, data); //copy address of data into the hashmap else *(hm->items)[index]->data = data; //if there is no resolve collision and there is data stored //store the data pointer as is } else { (hm->items)[index] = (HashMapItem *)malloc(sizeof(HashMapItem *)); (hm->items)[index]->data = (Data *)malloc(sizeof(Data *)); *(hm->items)[index]->data = data; } //allocate new space for the string key and copy it there (hm->items)[index]->key = strdup(key); } Data get_data(HashMap* hm, char* key){ if(key == NULL && hm == NULL) return NULL; unsigned int index = hash(key) % hm->size; if((hm->items)[index] == NULL){ return NULL; } return *(hm->items)[index]->data; } void remove_data(HashMap* hm, char * key, DestroyDataCallback destroy_data){ if(hm == NULL || key == NULL) return; unsigned int index = hash(key) % hm->size; if((hm->items)[index] == NULL) return; if(destroy_data != NULL){ destroy_data((Data) *(hm->items)[index]->data); } free((hm->items)[index]->data); free((hm->items)[index]->key); free((hm->items)[index]); } void test1() { HashMap *hm = create_hashmap(30); char *key ="jsart", *key_two = ":@-)", *key_three = "stonks"; // 修正原代码语法错误 int x = 69, y = 420; void *placeholder = &x, *placeholder_two = &y; insert_data(hm, key_three, placeholder, NULL); assert_that(get_data(hm,key_three) == placeholder); remove_data(hm,key_three,NULL); assert_ptr_equals(get_data(hm,key_three),NULL); assert_true((int*)get_data(hm,key_three)==NULL); //delete_hashmap(hm, destroy); } int main(){ test1(); return 0; }
原因分析
核心问题:remove_data未清空哈希表项指针
remove_data函数在释放HashMapItem及其内部的data、key指针后,没有将hm->items[index]设置为NULL,导致该位置的指针变成野指针(指向已释放的内存)。
当调用get_data时:
- 函数会先检查
(hm->items)[index] == NULL,但此时该位置是野指针,不等于NULL,因此进入后续逻辑。 - 函数尝试解引用
(hm->items)[index]->data,但data指针已经被free,这属于未定义行为——读取已释放内存的结果是随机垃圾值。
两个断言表现不同的原因
- 第一个断言成立是巧合:第一次调用
get_data返回的垃圾值刚好等于NULL(作为void*类型)。 - 第二个断言失败是因为第二次调用
get_data时,野指针指向的内存可能已被系统重新分配或修改,返回的垃圾值不再等于NULL;或是强转int*后,该垃圾值的二进制表示与int*类型的NULL不匹配(本质是该垃圾值并非空指针)。
额外代码问题
create_hashmap中hm->items分配错误:calloc(key_space, sizeof(HashMapItem**))应改为calloc(key_space, sizeof(HashMapItem*)),因为items是HashMapItem**,每个元素是HashMapItem*类型。insert_data中,哈希表项已存在时直接重新分配data指针,未释放原data内存,会导致内存泄漏。
修复方案
在remove_data函数最后添加一行,将哈希表项指针置空:
free((hm->items)[index]); hm->items[index] = NULL; // 新增该行
这样get_data就能正确识别该位置已无数据,返回NULL,两个断言都会成立。
内容的提问来源于stack exchange,提问作者Cristian Cutitei
相关产品推荐
相关产品推荐

