-1

I have written a small implementation of single linked list with a functionality of removing nodes with given values, based on the following articles/SO questions:

http://grisha.org/blog/2013/04/02/linus-on-understanding-pointers/

How do pointer to pointers work in 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);
  }
}

The problem lies with the removeIf() function on last line (main.c:68):

entry = entry->next;

The commented works and valgrind does not complain about it whereas the uncommented code works but valgrind complains as follows:

==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)

Can someone comment on what might be the problem?

Patryk
  • 22,602
  • 44
  • 128
  • 244
  • Can the person giving `-` comment? – Patryk May 06 '16 at 10:27
  • if I have to guess, it is because your code example is too much. You should also mark the lines 80, 100, 152 and 176 in your code. – mch May 06 '16 at 10:32
  • Sorry, I am not that person, I'm reviewing your code. @mch this lines are for backtrace calls, only the first one has the real problem. – OscarGarcia May 06 '16 at 10:36
  • @mch I have made the code sample smaller. – Patryk May 06 '16 at 10:37
  • Just a drive-by comment -- comments in `removeIf()` would be courteous. – DevSolar May 06 '16 at 10:44
  • I think that I did the work, I'm testing it. – OscarGarcia May 06 '16 at 10:48
  • The line `pp = &entry->next;` looks really suspect to me. What is it supposed to do, and why is it different from `*pp = entry->next;` in the other branch? – Bo Persson May 06 '16 at 10:52
  • In `append()`, the case where `(!head)` assigns to a function parameter. C is pass-by-value, the assignment does not affect the argument in the caller. – EOF May 06 '16 at 10:55
  • I think the problem is on `dd` variable that is not useful and it doesn't do any useful work, I'm trying to remove it, but with my modifications it fails: `tests: Assertion 'size(root) == 3' failed` :( – OscarGarcia May 06 '16 at 10:57
  • There is a problem with your code (commented one) that removes the node but doesn't update previous node to point next one, so that is the work that is supposed to do `dd`, I keep testing the code. – OscarGarcia May 06 '16 at 11:10
  • Your problem seems to be due to the missing `else` brackets. please, check my updated answer. – OscarGarcia May 06 '16 at 11:43
  • Negatives? Debug fail, probably. – Martin James May 06 '16 at 11:45

3 Answers3

1

I think I know what is going on. Let's review the flow step by step and assume that we encountered the very first node to remove (we will call it ENTRY). From your code that means the following state:

-- pp contains the address of an ENTRY pointer obvoiusly, since it's the first found, because pp was updated to the addresses of entry iterator all the time (we always had 'else' case).

What happens next? We assign ENTRY to tmp and pp to the next of ENTRY and then free tmp, essentially freeing memory of ENTRY since both tmp and ENTRY contain same address. This introduces two problems:

1) The previous node's next pointer (which is NODE->NEXT == ENTRY) becomes a dangling pointer since it has never been reassigned and current implementation of your function has no means to do it.

2) After you freed memory of ENTRY, you access the member 'next' to move your entry iterator forward. I assume that this is exact place and case where valgrind tells you that you accessed memory you have no ownership of.

HighPredator
  • 790
  • 4
  • 21
  • Pretty sure `*pp = entry->next;` *does* update the previous node's `next`-pointer, contrary to your point 1). However, your point 2) is correct, it's a use-after-free error. – EOF May 06 '16 at 11:51
  • @EOF, you may be correct, but right now I see only that up until the first valid element both pointers point at the same node because: both had same 'start point' (head) and both were iterated upwards at every loop iteration. – HighPredator May 06 '16 at 11:54
0

As per debugging with GDB

  1. removeif(head=0x7fffffffdee0,...)
  2. (gdb) p &entry $11 = (node )**0x7fffffffdec0
  3. Could you please check the entry and the head addresses? Am suspecting that 8 bytes is because of this.
techEmbedded
  • 2
  • 1
  • 3
0

This is your code fixed:

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;
    }
  }
}

Variable dd was not needed because it wasn't tracking previous node, so you can't relink previous one to next one and then free current node.

Your problem was the else brackets.

OscarGarcia
  • 1,995
  • 16
  • 17
  • Why the two negatives? It doesn't fix the issue? There is a better way to achieve it? Then, please, tell me how, comment me, but don't vote me negative without tell me why. Thanks. – OscarGarcia May 06 '16 at 11:23
  • Try using this to remove the *last* node. – EOF May 06 '16 at 11:24
  • @EOF Thanks! You are right, but with this code it is not possible to remove last node because it doesn't track the last node as commented code. It's time to lunch, I'll review the code and explain better why valgrind was complaining about original code. – OscarGarcia May 06 '16 at 11:28
  • This code is incorrect, because if one of the nodes with the payload `val` happens to be the last node, the function dereferences a `NULL`-pointer. This is undefined behavior (and crashes on many implementations). – EOF May 06 '16 at 11:33
  • @EOF, please, check it now. Now I'm tracking the last "next" field from last node or head. – OscarGarcia May 06 '16 at 11:37
  • Actually the problem was with updating `entry` **AFTER** it (through `tmp`). But yes - your code fixes it. – Patryk May 06 '16 at 12:11