在终止并正确返回正确输出的程序上找不到valgrind错误

时间:2018-07-01 21:33:11

标签: c valgrind

即使程序正确终止并为我提供正确的输出,并且没有内存泄漏,但valgrind还是给了我这种错误:

Invalid read of size 1
==910==    at 0x108DD4: fnv_hash_function (user.c:24)
==910==    by 0x108E17: hash (user.c:29)
==910==    by 0x109A50: icl_hash_find (icl_hash.c:114)
==910==    by 0x1094DB: db_request (user.c:197)
==910==    by 0x108D2E: main (tuser.c:65)
==910==  Address 0x5416f50 is 0 bytes inside a block of size 15 free'd
==910==    at 0x4C2E10B: free (vg_replace_malloc.c:530)
==910==    by 0x109152: freeKey (user.c:138)
==910==    by 0x109CF2: icl_hash_delete (icl_hash.c:192)
==910==    by 0x109796: db_request (user.c:222)
==910==    by 0x108CF8: main (tuser.c:59)
==910==  Block was alloc'd at
==910==    at 0x4C2CEDF: malloc (vg_replace_malloc.c:299)
==910==    by 0x108BDC: main (tuser.c:35)

hashfnv_hash_function是以这种方式定义的

static inline unsigned int fnv_hash_function( void *key, int len ) {
    unsigned char *p = (unsigned char*)key;
    unsigned int h = 2166136261u;
    int i;
    for ( i = 0; i < len; i++ )
        h = ( h * 16777619 ) ^ p[i]; //this is the line 24
    return h;
}


unsigned int hash(void *key){
    return fnv_hash_function(key, strlen(key));
}

我猜问题出在 ^ 运算符上,但是我无法弄清楚问题出在哪里,因为该程序以正确的输出终止并且没有分段错误。

icl_hash_find不是我编写的函数,而是在我正在使用的库中,并且以此方式定义

void *
icl_hash_find(icl_hash_t *ht, void* key)
{
    icl_entry_t* curr;
    unsigned int hash_val;

    if(!ht || !key) return NULL;

    hash_val = (* ht->hash_function)(key) % ht->nbuckets;

    for (curr=ht->buckets[hash_val]; curr != NULL; curr=curr->next)
        if ( ht->hash_key_compare(curr->key, key))
            return(curr->data);

    return NULL;
}

我在该库的测试套件上尝试了valgrind,但未发现任何错误,因此我怀疑问题出在哪里。

编辑: 密钥在此for循环中分配:

char * s; //string used as key
for(int i = 0; i < N; i++){

    s = (char *)malloc(NAMELEN * sizeof(char));
    sprintf(s, "Utente %d", i);
    u = create_user( s , i);

    if(!db_request(db, s, u, PUT)){
        perror("problema PUT");
        exit(EXIT_FAILURE);
    }
    .
    .
    .

编辑2:这是db_request的主体:

bool db_request(userbase_t *db, char * key, user_t * u, dbop_t op ){

if(db==NULL || key == NULL ||(op!=DELETE && u==NULL)){
    errno = EINVAL;
    return false;
}
int lock_index; //indice del lock del bucket
switch(op){
    //implementazione PUT
    case PUT : 
        lock_index = db -> table -> hash_function(key) % db->nlocks;
        WLOCK(&db->locks[lock_index])
        errno = 0;
        if(icl_hash_insert(db->table, key, (void *) u)==NULL){
            RWUNLOCK(&db->locks[lock_index])
            //la chiave e' gia' associata ad un utente
            if(errno == EINVAL){
                perror("key gia' presente");
            }
            return false;
        }
        RWUNLOCK(&db->locks[lock_index])
        return true;
    //implementazione GET
    case GET :
        lock_index = db -> table -> hash_function(key) % db->nlocks;
        RLOCK(&db->locks[lock_index])

        u = icl_hash_find(db->table, (void *)key );

        RWUNLOCK(&db->locks[lock_index]);
        return true;
    //implementazione update
    case UPDATE :
        //elimina il vecchio e aggiunge il nuovo
        lock_index = db -> table -> hash_function(key) % db->nlocks;
        WLOCK(&db->locks[lock_index]);

        if(icl_hash_delete(db->table, key, freeKey, freeUser)){
            perror("problema UPDATE (icl_hash_delete) ");
            RWUNLOCK(&db->locks[lock_index]);
            return false;
        }

        if (icl_hash_insert(db->table, key, (void *) u)==NULL){
            perror("problema UPDATE (icl_hash_insert)");
            RWUNLOCK(&db->locks[lock_index]);
            return false;
        }
    case DELETE :
        lock_index = db -> table -> hash_function(key) % db->nlocks;
        WLOCK(&db->locks[lock_index]);          

        if(icl_hash_delete(db->table, key, freeKey, freeUser)){
            perror("problema DELETE");
            RWUNLOCK(&db->locks[lock_index]);
            return false;
        }

        RWUNLOCK(&db->locks[lock_index]);
        return true;
    //mai raggiunto
    default :
        errno = EINVAL;
        perror("problema switch op");
        return false;

}}

但是我找不到问题,我开始认为问题出在icl_hash库中。

当我在测试函数中刚刚删除的元素上调用GET时,就会发生问题。

if(!db_request(db, s , u ,DELETE)){
            perror("problema DELETE");
            exit(EXIT_FAILURE);
        };

//provo a ottenerlo di nuovo
//The error happens here
if(!db_request(db, s , u ,GET)){
    perror("GET");
    exit(EXIT_FAILURE);
};

get唯一要做的就是调用此函数:

void *
icl_hash_find(icl_hash_t *ht, void* key)
{
icl_entry_t* curr;
unsigned int hash_val;

if(!ht || !key) return NULL;

hash_val = (* ht->hash_function)(key) % ht->nbuckets;

for (curr=ht->buckets[hash_val]; curr != NULL; curr=curr->next)
    if ( ht->hash_key_compare(curr->key, key))
        return(curr->data);

return NULL;
}

2 个答案:

答案 0 :(得分:2)

您无效的访问是对已经释放的内存的访问:

Invalid read of size 1
==910==    at 0x108DD4: fnv_hash_function (user.c:24)
==910==    by 0x108E17: hash (user.c:29)
==910==    by 0x109A50: icl_hash_find (icl_hash.c:114)
==910==    by 0x1094DB: db_request (user.c:197)
==910==    by 0x108D2E: main (tuser.c:65)
==910==  Address 0x5416f50 is 0 bytes inside a block of size 15 free'd
==910==    at 0x4C2E10B: free (vg_replace_malloc.c:530)
==910==    by 0x109152: freeKey (user.c:138)
==910==    by 0x109CF2: icl_hash_delete (icl_hash.c:192)
==910==    by 0x109796: db_request (user.c:222)
==910==    by 0x108CF8: main (tuser.c:59)
==910==  Block was alloc'd at
==910==    at 0x4C2CEDF: malloc (vg_replace_malloc.c:299)
==910==    by 0x108BDC: main (tuser.c:35)

在对文件icl_hash_delete()中函数db_request()中的第user.c行的icl_hash_find()调用中释放了该块。在db_request()user.c中第197行的icl_hash*调用中进行了无效访问。您说db_request()代码已提供给您。

没有功能icl_hash*的主体是很难确定发生了什么,但是至少有两种可能。

  1. 您已经悬挂了指向已删除条目的指针,并且您不应该继续使用它。
  2. icl_hash.c函数中的代码在删除哈希记录后对数据进行了错误处理。

如果db_request()函数的提供者是相当可靠的,则可以合理地假设问题出在icl_hash_find()中的代码中。特别要仔细看第197和222行,以及传递给icl_hash_delete()icl_hash.c函数的变量(指针)。再次查看这些功能的手册页,以了解规则。

如果不确定icl_hash.c中的代码质量,则应为自己创建一个较小的MCVE,该MCVE可创建哈希表,添加一些行,查找一些行,删除一些条目并执行一些操作更多发现。这将帮助您确定代码中或main()代码中是否存在问题。

由于内存是在db_request()中分配的,而不是在db_request()中分配的,因此您可能必须查看该级别的操作,但是我的猜测是您将该指针传递给了{{1} },并在您添加条目时将其所有权移交给哈希表,icl_hash_delete()的规范说它将释放您交给哈希表的内存,并且您仍然不小心持有了指向现在释放的指针内存(db_request()函数的参数)。您必须非常小心,以确保您知道谁拥有什么内存,以及何时释放该内存。

答案 1 :(得分:1)

Valgrind告诉您fnv_hash_function函数有问题。在此功能中,您可以在freeKey中释放(取消分配)内存后再访问它。它甚至告诉您您恰好在该内存块的开头访问它。您在main (tuser.c:35)中分配了该内存。您的程序偶然起作用。只要有问题的内存块不会在内存页面的开始处结束并且它将是该页面上释放的最后一个块,它将继续工作-然后该页面将(可能取决于分配器策略) )从您的处理空间中取消映射。取消映射后,如果您访问它(如fnv_hash_function),则程序将在您尝试访问它的那一刻完全崩溃。

何时该块可以作为页面上的第一个块结束并成为最后一个要释放的块? -很难说,在您对源代码或基础库(包括您无法控制的系统库)进行任何更改之后,可能会发生这种情况。