3

While working with linked lists in C I noticed this behavior that I don't understand. The example code below illustrates the situation where a simple list is declared and filled with nodes that contain *char names. theName string is generated by appending each of the parameters given in the command line by _, so charNum is greater than argv[i] by 2 to accommodate _ and \0. Each of argv elements generates a node that is added to the list in the for loop in the main function.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct node {
  char* name;
  struct node* next;
};

struct node*
nalloc(char* name)
{
  struct node* n = (struct node*) malloc(sizeof(struct node));
  if (n)
  {
    n->name = name;
    n->next = NULL;
  }
  return n;
}

struct node*
nadd(struct node* head, char* name)
{
  struct node* new = nalloc(name);
  if (new == NULL) return head;
  new->next = head;
  return new;
}

void
nprint(struct node* head)
{
  struct node* n = NULL;
  printf("List start: \n");
  for(n = head; n; n=n->next)
  {
    printf("  Node name: %s, next node: %p\n", n->name, n->next);
  }
  printf("List end. \n");
}

void
nfree(struct node* head)
{
  struct node* n = NULL;
  printf("Freeing up the list: \n");
  while (head)
  {
    n = head;
    printf("  Freeing: %s\n", head->name);
    head = head->next;
    free(n);
  }
  printf("Done.\n");
}

int
main(int argc, char** argv)
{
  struct node* list = NULL;
  char* theName = (char*) malloc(0);
  int i, charNum;
  for (i=0; i < argc; i++)
  {
    charNum = strlen(argv[i]) + 2;
    theName = (char*) realloc(NULL, sizeof (char)*charNum);
    snprintf(theName, charNum, "%s_", argv[i]);
    list = nadd(list, theName);
  }
  nprint(list);
  nfree(list);
  free(theName);
  return 0;
}

The code above works as one would expect:

$  ./a.out one two three
List start: 
  Node name: three_, next node: 0x1dae0d0
  Node name: two_, next node: 0x1dae090
  Node name: one_, next node: 0x1dae050
  Node name: ./a.out_, next node: (nil)
List end. 
Freeing up the list: 
  Freeing: three_
  Freeing: two_
  Freeing: one_
  Freeing: ./a.out_
Done.

However when I modify this code and call free(theName) before printing the list:

  ...
  free(theName);
  nprint(list);
  nfree(list);
  return 0;
  ...

the name of the last list item is missing:

$  ./a.out one two three
List start: 
  Node name: , next node: 0x3f270d0
  Node name: two_, next node: 0x3f27090
  Node name: one_, next node: 0x3f27050
  Node name: ./a.out_, next node: (nil)
List end. 
Freeing up the list: 
  Freeing: 
  Freeing: two_
  Freeing: one_
  Freeing: ./a.out_
Done.

So freeing up theName pointer affected the list node that was using it as its name, but how come earlier reallocs didn't affect the other nodes? If free(theName) broke the last node's name I would guess that realloc would do the same and all nodes from the list would have blank names.


Thank you all for your comments and answers. I modified the code to remove casting of malloc's results, add freeing of node->name and change 'malloc -> multiple reallocs -> free' to 'multiple mallocs -> free' for the name. So here's the new code:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct node {
  char* name;
  struct node* next;
};

struct node*
nalloc(char* name)
{
  struct node* n = malloc(sizeof(struct node));
  if (n)
  {
    n->name = name;
    n->next = NULL;
  }
  return n;
}

struct node*
nadd(struct node* head, char* name)
{
  struct node* new = nalloc(name);
  if (new == NULL) return head;
  new->next = head;
  return new;
}

void
nprint(struct node* head)
{
  struct node* n = NULL;
  printf("List start: \n");
  for(n = head; n; n=n->next)
  {
    printf("  Node name: %s, next node: %p\n", n->name, n->next);
  }
  printf("List end. \n");
}

void
nfree(struct node* head)
{
  struct node* n = NULL;
  printf("Freeing up the list: \n");
  while (head)
  {
    n = head;
    printf("  Freeing: %s\n", head->name);
    head = head->next;
    free(n->name);
    free(n);
  }
  printf("Done.\n");
}

int
main(int argc, char** argv)
{
  struct node* list = NULL;
  char* theName;
  int i, charNum;
  for (i=0; i < argc; i++)
  {
    charNum = strlen(argv[i]) + 2;
    theName = malloc(sizeof (char)*charNum);
    snprintf(theName, charNum, "%s_", argv[i]);
    list = nadd(list, theName);
  }
  nprint(list);
  nfree(list);
  free(theName);
  return 0;
}

The above works as expected:

$  ./a.out one two three
List start: 
  Node name: three_, next node: 0x1826c0b0
  Node name: two_, next node: 0x1826c070
  Node name: one_, next node: 0x1826c030
  Node name: ./a.out_, next node: (nil)
List end. 
Freeing up the list: 
  Freeing: three_
  Freeing: two_
  Freeing: one_
  Freeing: ./a.out_
Done.

However when I place free(theName); before nprint(list);:

  free(theName);
  nprint(list);
  nfree(list);
  return 0;

In the output the last node's name is missing and nfree(list); throws error:

$  ./a.out one two three
List start: 
  Node name: , next node: 0x1cf3e0b0
  Node name: two_, next node: 0x1cf3e070
  Node name: one_, next node: 0x1cf3e030
  Node name: ./a.out_, next node: (nil)
List end. 
Freeing up the list: 
  Freeing: 
*** glibc detected *** ./a.out: double free or corruption (fasttop): 0x000000001cf3e0d0 ***
======= Backtrace: =========
...
======= Memory map: ========
...
Aborted

When I put free(theName); after nprint(list); and before nfree(list);:

  nprint(list);
  free(theName);
  nfree(list);
  return 0;

in the output all nodes are printed properly, but nprint(list); still throws the error:

$  ./a.out one two three
List start: 
  Node name: three_, next node: 0x19d160b0
  Node name: two_, next node: 0x19d16070
  Node name: one_, next node: 0x19d16030
  Node name: ./a.out_, next node: (nil)
List end. 
Freeing up the list: 
  Freeing: 
*** glibc detected *** ./a.out: double free or corruption (fasttop): 0x000000001cf3e0d0 ***
======= Backtrace: =========
...
======= Memory map: ========
...
Aborted

This raises another question in my mind: I'm guessing that in any case the memory pointed by theName is freed twice: first as node->name and second as theName, so how come free(theName); does not raise the double-free error when called at the end of the program after nfree(list); (as it is in the working code)?

Brad Larson
  • 170,088
  • 45
  • 397
  • 571
mac13k
  • 2,423
  • 23
  • 34
  • This `realloc(NULL, sizeof (char)*charNum);` is equivalent to `malloc(sizeof (char)*charNum);`. – alk Mar 20 '14 at 16:03
  • This `char* theName = (char*) malloc(0);` might leak memory, depending on the libc implementation. – alk Mar 20 '14 at 16:04
  • 1
    In C there is **no** need to cast the results of `malloc/calloc/realloc` **nor** is it recommended. – alk Mar 20 '14 at 16:05

2 Answers2

4

When you free theName, the pointer still points to the name portion most recent addition to the list. It doesn't point at the earlier items in the list because the pointers are properly being managed by the structure elements, and theName was moved to point at a different value (the most recent addition). This is why the name is being free()d.

You're also leaking memory from not properly freeing up the variables inside each struct element (namely name) before freeing the struct element itself. I would personally recommend getting valgrind (linux) or this (windows) and running your program through it.

Community
  • 1
  • 1
ciphermagi
  • 747
  • 3
  • 14
  • You're right, I was overlooking that - indeed `nfree` does not free the names! However, are you sure about that leaking? Conceptually, isn't there is only one block of memory which is allocated, then reallocated several times and finally freed? – Codor Mar 20 '14 at 15:19
  • @Codor Remember that when you deconstruct, you must do it in reverse. free() the innermost memory pointer first, then the next most inner level, etc, otherwise you will lose your references to the variables that keep track of the inner pointers, and have a leak again. – ciphermagi Mar 20 '14 at 15:23
  • @Codor You're actually using realloc() like malloc(). If it were actually reallocating the way you think it is, your program would break in a lot of other interesting ways. – ciphermagi Mar 20 '14 at 16:05
  • @ciphermagi Does it mean, that when `theName` is reallocated at each iteration, `name` of each node points to the memory location that has been freed? If so is there a risk that this memory may be overwritten due the course of the program's runtime and nodes' names will show different values? – mac13k Mar 27 '14 at 19:01
3

To my understanding, if you call free(theName) before printing the list, the memory pointed to by the last list node is freed. Furthermore I am a bit skeptical about using realloc to allocate new memory; when printing the list, you might read memory that contains the expected data, but already has been deallocated by realloc.

Note that realloc is permitted to move the starting address of the memory block, which means that the old content may be still there even when writing to the address returned by realloc.

Codor
  • 17,447
  • 9
  • 29
  • 56
  • realloc() is being used properly. There won't be any memory loss. – ciphermagi Mar 20 '14 at 15:21
  • Well, I haven't used C for several years now. Do I understand correctly that there is no memory leak, but in the output of the list the names are most likely read from memory already freed? – Codor Mar 20 '14 at 15:28
  • No, there's definitely a memory leak, but it's nothing to do with the use of realloc(), which simply moves allocated memory to a suitable contiguous block for the size of the new allocation, then properly frees the original memory, maintaining the integrity of the pointer on its own. – ciphermagi Mar 20 '14 at 15:31
  • I agree concerning the usage of realloc(), however I still believe there is no memory leak. Please help me to understand or to make myself a little bit more clear. To my understanding there is no problem with the memory management of the list, nodes are allocated and freed in a consistent manner; no alloc for the name member, no free for the name member. However the allocation and deallocation for the name member is also consistent in the sense that there is no memory leak, but the reading access in the output acesses memory which has already been freed? – Codor Mar 20 '14 at 15:37
  • The name member stays allocated. It's a side-effect allocation, and indirectly accidental at best. I would never have done this, instead preferring to make all of my allocations with `malloc()` inside of the `nalloc()` helper. – ciphermagi Mar 20 '14 at 15:40
  • I think what's confusing you is the realloc() syntax. The first element is NULL, which makes it behave as a malloc(), since no pointer was passed in. – ciphermagi Mar 20 '14 at 15:45
  • Oh, I see! It is indeed somewhat subtle! Thanks for your patience, now I fully agree with your explanation. – Codor Mar 20 '14 at 15:50