You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

将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时:

  1. 函数会先检查(hm->items)[index] == NULL,但此时该位置是野指针,不等于NULL,因此进入后续逻辑。
  2. 函数尝试解引用(hm->items)[index]->data,但data指针已经被free,这属于未定义行为——读取已释放内存的结果是随机垃圾值。

两个断言表现不同的原因

  • 第一个断言成立是巧合:第一次调用get_data返回的垃圾值刚好等于NULL(作为void*类型)。
  • 第二个断言失败是因为第二次调用get_data时,野指针指向的内存可能已被系统重新分配或修改,返回的垃圾值不再等于NULL;或是强转int*后,该垃圾值的二进制表示与int*类型的NULL不匹配(本质是该垃圾值并非空指针)。

额外代码问题

  1. create_hashmap中hm->items分配错误:calloc(key_space, sizeof(HashMapItem**))应改为calloc(key_space, sizeof(HashMapItem*)),因为items是HashMapItem**,每个元素是HashMapItem*类型。
  2. insert_data中,哈希表项已存在时直接重新分配data指针,未释放原data内存,会导致内存泄漏。

修复方案

在remove_data函数最后添加一行,将哈希表项指针置空:

free((hm->items)[index]);
hm->items[index] = NULL; // 新增该行

这样get_data就能正确识别该位置已无数据,返回NULL,两个断言都会成立。

内容的提问来源于stack exchange,提问作者Cristian Cutitei

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 18:20:20