[英]Why valgrind complains about Invalid reads of size 8 on single linked list node removal?
根据以下文章/SO问题,我编写了一个单链表的小型实现,具有删除具有给定值的节点的功能:
http://grisha.org/blog/2013/04/02/linus-on-understanding-pointers/
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
typedef struct node
{
int val;
struct node* next;
} node;
node* newNode(int val)
{
node* n = (node*)malloc(sizeof(node));
n->val = val;
n->next = NULL;
return n;
}
void clean(node* head)
{
while (head)
{
node* curr = head;
head = head->next;
free(curr);
}
}
void append(node* head, int val)
{
if(head == NULL)
{
head = newNode(val);
return;
}
while (head->next)
head = head->next;
head->next = newNode(val);
}
void removeIf(node** head, int val)
{
//node **curr = head;
//while (*curr != NULL) {
// if ((*curr)->val == val) {
// node *next = (*curr)->next;
// free(*curr);
// *curr = next;
// } else {
// curr = &((*curr)->next);
// }
//}
node **pp = head; /* pointer to a pointer */
node *entry = *head;
while (entry) {
if (entry->val == val)
{
node* tmp = *pp;
*pp = entry->next;
free(tmp);
}
else
pp = &entry->next;
entry = entry->next;
}
}
int size(node* head)
{
int sz = 0;
while (head)
{
head = head->next;
++sz;
}
return sz;
}
void tests();
int main()
{
tests();
return 0;
}
/*
* Here are the tests for lists functionalities
*/
void tests()
{
{
node* root = newNode(1);
append(root, 1);
append(root, 1);
append(root, 1);
append(root, 2);
append(root, 3);
append(root, 5);
assert(size(root) == 7);
removeIf(&root, 1);
assert(size(root) == 3);
clean(root);
}
}
问题在于最后一行( main.c:68
)的removeIf()
函数:
entry = entry->next;
注释的作品和 valgrind 没有抱怨它,而未注释的代码有效,但 valgrind 抱怨如下:
==31665== Memcheck, a memory error detector
==31665== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==31665== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==31665== Command: ./a.out
==31665==
==31665== Invalid read of size 8
==31665== at 0x40067C: removeIf (main.c:68)
==31665== by 0x400785: tests (main.c:106)
==31665== by 0x4006C7: main (main.c:88)
==31665== Address 0x4c33048 is 8 bytes inside a block of size 16 free'd
==31665== at 0x4A06430: free (vg_replace_malloc.c:446)
==31665== by 0x400669: removeIf (main.c:63)
==31665== by 0x400785: tests (main.c:106)
==31665== by 0x4006C7: main (main.c:88)
==31665==
==31665==
==31665== HEAP SUMMARY:
==31665== in use at exit: 0 bytes in 0 blocks
==31665== total heap usage: 7 allocs, 7 frees, 112 bytes allocated
==31665==
==31665== All heap blocks were freed -- no leaks are possible
==31665==
==31665== For counts of detected and suppressed errors, rerun with: -v
==31665== ERROR SUMMARY: 4 errors from 1 contexts (suppressed: 6 from 6)
有人可以评论可能是什么问题吗?
我想我知道发生了什么。 让我们逐步回顾一下流程并假设我们遇到了要删除的第一个节点(我们将其称为 ENTRY)。 从您的代码中,这意味着以下状态:
-- pp 显然包含一个 ENTRY 指针的地址,因为它是第一个找到的,因为pp 一直更新为入口迭代器的地址(我们总是有 'else' 情况)。
接下来发生什么? 我们将 ENTRY 分配给 tmp,将 pp 分配给 ENTRY 的下一个,然后释放 tmp,本质上是释放 ENTRY 的内存,因为tmp 和 ENTRY 都包含相同的地址。 这引入了两个问题:
1) 前一个节点的下一个指针(即 NODE->NEXT == ENTRY)成为一个悬空指针,因为它从未被重新分配过,并且您的函数的当前实现无法做到这一点。
2) 释放 ENTRY 的内存后,访问成员“next”以向前移动条目迭代器。 我认为这正是 valgrind 告诉您您访问了您没有所有权的内存的确切位置和情况。
根据 GDB 调试
这是您修复的代码:
void removeIf(node** head, int val)
{
node** last_next_pointer = head;
node* entry = *head;
while (entry != NULL) {
if (entry->val == val) {
node* tmp = entry;
*last_next_pointer = entry->next;
entry = entry->next;
free(tmp);
} else {
last_next_pointer = &(entry->next);
entry = entry->next;
}
}
}
不需要变量dd
,因为它不跟踪前一个节点,因此您不能将前一个节点重新链接到下一个节点,然后释放当前节点。
你的问题是else
括号。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.