3

I want to dynamically add new elements to a array of structs. I have added a minimal example that segfaults. I think I have to pass a pointer struct data **arr to the function to reallocate and add new element correctly, but I haven't been able to do it correctly.

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

struct data {
    char s[10];
    int arr[10];
};

void add_one_element(struct data *arr, int *num_elements)
{
  /*increment counter*/
  *num_elements +=1;
  arr = realloc(arr, *num_elements * sizeof(struct data));

  strcpy(arr[*num_elements-1].s, "ABC");
  for(int i = 0; i < 10; i++)
    arr[*num_elements-1].arr[i] = i;
}


int main()
{
   struct data *arr = NULL;
   int num_elements = 0;
   add_one_element(arr, &num_elements);

   printf("arr.s = %s\n", arr[num_elements-1].s);
   for(int i = 0; i < 10; i++)
        printf("arr[%d].arr[%d] = %d\n", num_elements-1, i, arr[num_elements-1].arr[i]);

   free(arr);
   return 0;
}

EDIT 1: Tried to correct the problem. This time I get test(91537,0x7fff79532000) malloc: *** error for object 0x7fff5f5c0ad0: pointer being realloc'd was not allocated. Which suggest that the reallocation failed.

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

struct data {
    char s[10];
    int arr[10];
};

void add_one_element(struct data **arr, int *num_elements)
{
  /*increment counter*/
  *num_elements +=1;
  arr = realloc(arr, *num_elements * sizeof(struct data));

  strcpy(arr[*num_elements-1]->s, "ABC");
  for(int i = 0; i < 10; i++)
    arr[*num_elements]->arr[i] = i;
}


int main()
{
   struct data *arr = NULL;
   int num_elements = 0;
   add_one_element(&arr, &num_elements);
   printf("arr.s => %s\n", arr[num_elements-1].s);
   for(int i = 0; i < 10; i++)
        printf("arr[%d].arr[%d] = %d\n", num_elements-1, i, arr[num_elements-1].arr[i]);
   return 0;
}
4
  • You are reallocating the address of a local variable instead of arr in main, pass a pointer to pointer: void add_one_element(struct data **arr, int *num_elements) and add_one_element(&arr, &num_elements); Commented Aug 24, 2016 at 18:41
  • Carefully stepping through your code in a debugger would quickly point out the problem to you. Commented Aug 24, 2016 at 18:46
  • Something like arr = realloc(arr, ... is very bad practice! What if realloc returns a null pointer? You loose the array. First thing is to have proper error handling! Commented Aug 24, 2016 at 18:47
  • 1
    Note that repeatedly adding one to an array with realloc() can lead to quadratic performance over time, in general, as it has to copy all the previous entries to the new location — in general. The normal technique is to (a) keep a record of the number of allocated entries as well as the number of entries in use, and (b) double the number of allocated entries when you need to allocate more. If, when you're finished adding, there is enough extra allocated space to warrant it, you could use a shrinking realloc() to return the unused space. 'Enough' — depends; think 256 bytes or more, perhaps. Commented Aug 24, 2016 at 18:52

2 Answers 2

3

You're correct that you need to pass in a struct data ** to your function. You would do it as follows:

void add_one_element(struct data **arr, int *num_elements)
{
  /*increment counter*/
  *num_elements +=1;
  *arr = realloc(*arr, *num_elements * sizeof(struct data));

  strcpy((*arr)[*num_elements-1].s, "ABC");
  for(int i = 0; i < 10; i++)
    (*arr)[*num_elements-1].arr[i] = i;
}

So anyplace you were referencing arr in this function would change to *arr. Note that this expression is parenthesized in order to access the array elements.

Then you call the function like this:

add_one_element(&arr, &num_elements);
Sign up to request clarification or add additional context in comments.

Comments

2

Since C passes by value, you cannot change the variable value for the caller without a reference. In C, this is done by dereferencing a pointer to the variable that the function needs to change. Thus, you need to pass in the pointer to the pointer variable.

(In your edited change, you did not dereference the pointer variable.)

realloc may return NULL, and in that case, the original pointer has not been freed. So, you need to capture and test the result or realloc in a separate variable.

void add_one_element(struct data **arr, int *num_elements)
{
  struct data *tmp;
  int tmp_num = *num_elements + 1;

  /*increment counter*/
  tmp = realloc(*arr, tmp_num * sizeof(struct data));
  if (tmp == NULL) {
      /* XXX: handle the error */
      return;
  }

  strcpy(tmp[tmp_num-1].s, "ABC");
  for(int i = 0; i < 10; i++)
    tmp[tmp_num-1].arr[i] = i;

  *arr = tmp;
  *num_elements = tmp_num;
}

5 Comments

Alternatively, one could simply force an abnormal termination if the realloc returns null if targeting an implementation where a realloc failure would indicate that an unrecoverable failure was likely imminent.
@supercat: That is a valid decision to make, but not something I would want an API that happens to perform allocation to do. I would rather the failure get logged and propagated back to the caller to decide if termination is required.
If a program's requirements would be satisfied if it terminates abnormally in case of memory shortage, but would not be satisfied if it writes to memory it does not own, an allocation API which is guaranteed to either succeed or terminate abnormally can make it easier to meet those requirements than would be possible on one that requires the caller to check every call for possible failure.
Further, some virtual memory systems only allocate physical pages to portions of a malloc block if/when they're accessed. Such designs can work very well if code makes large allocations and ends up abandoning them while leaving significant chunks unused, but make it impossible to reliably recover from out-of-memory conditions. If situations where malloc() or realloc() returns null are less common than those where out-of-memory faults cause a program to die when dereferencing a seemingly-valid pointer, there's not much point trying to gracefully handle the former.
@supercat: There are exceptions, I am simply stating what I would prefer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.