簡體   English   中英

為什么 valgrind 在單個鏈表節點刪除時抱怨大小為 8 的無效讀取?

[英]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/

指向指針的指針如何在 C 中工作?

#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 調試

  1. 刪除(頭= 0x7ffffffffdee0 ,...)
  2. (gdb) p &entry $11 = (node )**0x7fffffffdec0
  3. 你能檢查一下入口和頭地址嗎? 我懷疑 8 個字節是因為這個。

這是您修復的代碼:

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM