Search code examples
c++functionreallocpointer-to-pointer

Necessity of double pointer when using realloc to manipulate array


I am new to C++ programming and want to try out manual memory management (I am aware that it is advised not to use manual memory management, but I still want to give it a try).

My goal was to write a function that can append an element to an array by using realloc.

I use this struct:

struct pathToCity{  
  int lengthCities;
  char cities[100];
};

Here is my function:

void appendToPathToCitiesArray(pathToCity **array, int *length, pathToCity newCity) {
  if (*length == 0) {
    *array = (pathToCity*) malloc(sizeof(pathToCity));
  }
  else {
    *array = (pathToCity*) realloc(*array, (*length + 1) * sizeof(pathToCity));
  }
  if (array == NULL) {
    cout << "Error allocating memory." << endl;
    exit(-1);
  }
  (*array)[*length] = newCity;
  (*length)++;
}

Here I define an array:

int lengthQueue = 0;
    pathToCity *queue;

Here I instantiate an instance of my struct:

pathToCity newPath = {0, ""};

Finally I use my function:

appendToPathToCitiesArray(&queue, &lengthQueue, newPath);

This function works fine as it is using double pointers. According to my research (unfortunately I cannot find the stackoverflow post where this was explained) double pointers here are necessary, because once a pointer gets passed to a function, it is impossible to change the pointer inside the function. Therefore double pointers are required once I want to use realloc to update my array.

But I have also tried this in a different file:

#include <iostream>


using namespace std;



struct shortText {
    int lengthText;
    char text[3];
};


void append(shortText *array, int *length, shortText newCity) {
  if (*length == 0) {
    array = (shortText*) malloc(sizeof(shortText));
  }
  else {
    array = (shortText*) realloc(array, (*length + 1) * sizeof(shortText));
  }
  if (array == NULL) {
    cout << "Error allocating memory" << endl;
    exit(-1);
  }
  array[*length] = newCity;
  (*length)++;
}


int main() {

  int length = 3;
  shortText *arr = (shortText*) malloc(sizeof(shortText) * length);
  
  arr[0] = {2, "ab"};
  arr[1] = {2, "ab"};
  arr[2] = {2, "ab"};

  shortText s = {2, "ab"};
  append(arr, &length, s);

  arr[3] = {8, "ji"};

  cout << length << endl;
  cout << arr[3].lengthText << endl;





  free(arr);
}

It also works perfectly and does not use double pointers. Why are double pointers necessary in one case and why not in the other?

If you require any more information, please ask. I have not provided the entire first code, to give a better overview, if I have missed out an important part, I will post the rest of the code.


Solution

  • Using the second function implementation invokes undefined behavior.

    It just occurred such a way that the called function realloc did not change the address stored in the pointer arr. That is the function just enlarged the initial extent of memory. But in general it can allocate another extent of memory at a different address. In this case the pointer arr declared in main will be invalid.

    You need to pass a pointer to pointer because dereferencing the pointer to pointer you get a direct access to the original pointer arr and thus can change it within the function. Otherwise the function will deal with a copy of the value of the original pointer and changing the copy will not influence on the original pointer.

    It is the so-called pass by reference in the C meaning.

    Compare these two demonstration program.

    #include <iostream>
    
    typedef int T;
    
    void f( T x )
    {
        x = T();
    }
    
    int main()
    {
        T x = 10;
    
         std::cout << "Before calling f() x = " << x << '\n';
    
         f( x );
    
         std::cout << "After  calling f() x = " << x << '\n';
    }
    

    and

    #include <iostream>
    
    typedef int T;
    
    void f( T *x )
    {
        *x = T();
    }
    
    int main()
    {
        T x = 10;
    
         std::cout << "Before calling f() x = " << x << '\n';
    
         f( &x );
    
         std::cout << "After  calling f() x = " << x << '\n';
    }
    

    There is used the typedef intentially

    typedef int T;
    

    to show that instead of the type int there can be used also a pointer type as for example int *

    typedef int * T;
    

    Only in main you should write

    int value;
    T x = &value;
    

    and call the function f in the both demonstration programs.

    In C++ you could just pass the pointer by reference like

    void append(shortText * &array, int &length, shortText newCity);
    

    Pay attention to that this call of realloc

    array = (shortText*) realloc(array, (*length + 1) * sizeof(shortText));
    

    is unsafe and can result in a memory leak if the function will be unable to allocate a new extent of memory and will return a null pointer. You should use an intermediate pointer as for example

    shortText *tmp = (shortText*) realloc(array, (*length + 1) * sizeof(shortText));
    
    if ( tmp != nullptr ) array = tmp;