Search code examples
cdynamic-memory-allocationallocationdynamic-arraysrealloc

C : Realloc doesn't work with dynamic double pointer array


I am facing some issues regarding a realloc with a double pointer dynamic array. What I would like to perform is to add 2 pointers of type Flight* inside the array schedule of type Flight **.

For that, I am relying on the function add_flight in the Functions.c file. This function asks the user for the airline and flight number values and stores these data in a new Flight* f. If the schedule is null (no flight yet added) it allocates memory for the newly created flight otherwise it realloc the size of schedule in order the add the new flight.

Main.c file:

int main() {
    int choice = 1;
    Flight** schedule = NULL;   

    printf("---AIRPORT MANAGER---");
    schedule = add_flight(schedule);
    printf("\n%s : %d\n", (*schedule)->airline, (*schedule)->flightNumber);
    schedule = add_flight(schedule);
    printf("\n%s : %d\n", (*schedule + 1)->airline, (*schedule)->flightNumber);

    return 0;
}

Functions.c file :

#include "Functions.h"

void mygets(char* s, int maxLength) {
    fflush(stdout);
    if (fgets(s, maxLength, stdin) != NULL) {
        size_t lastIndex = strlen(s) - 1;
        if (s[lastIndex] == '\n')
            s[lastIndex] = '\0';
    }
}

void flush() {
    char buffer;
    while ((buffer = getchar()) != EOF && buffer != '\n');
}

Flight** add_flight(Flight** schedule) {
    Flight* f;
    char buffer[100];

    if ((f = (Flight*)malloc(sizeof(Flight*))) == NULL) {
        exit(1);
    }

    printf("\n\n---FLIGHT CREATION---");
    printf("\nAirline: ");
    mygets(buffer, sizeof(buffer));
    if ((f->airline = _strdup(buffer)) == NULL) {
        exit(1);
    }
    memset(buffer, 0, 100);

    printf("\nFlight number: ");
    scanf("%d", &f->flightNumber);
    flush();

    if (schedule == NULL) {
        if ((schedule = malloc(sizeof(Flight*))) == NULL) {
            exit(1);
        }       
        *schedule = f;
    }
    else {
        int numberFlights = ((sizeof(*schedule)) / 4) + 1;
        if ((schedule = realloc(schedule, numberFlights * sizeof(Flight*))) == NULL) {
            exit(1);
        }
        *(schedule + numberFlights -1) = f;
    }

    return schedule;
}

The issue comes when the second call of add_flight is performed in the main.c

In the add_flight function, the data are indeed stored in the new Flight* f and then the else statement is considered: the variable numberFlights gets the value 2. However, the realloc doesn't work, the schedule is not enlarged and thus there is still only the first flight stored inside this schedule array. I can't figure out why the second flight is not added inside the schedule.

Can someone explain me why this realloc fails ? Thanks for your help :)


Solution

  • The sizeof operator is evaluated at compile time. It cannot be used to determine the size of a dynamically allocated array.

    C imposes the burden of keeping track of the actual size of an array onto the programmer. You could kee a separate count variable, but because the actual array and its size belong together, it is useful to store them alongside each other in a struct:

    typedef struct Flight Flight;
    typedef struct Flights Flights;
    
    struct Flight {
        char airline[4];
        int number;
        char dest[4];
    };
    
    struct Flights {
        Flight *flight;
        int count;
    };
    

    Instead of operating on the array, operate on the struct:

    void add_flight(Flights *fl,
        const char *airline, int number, const char *dest)
    {
        int n = fl->count++;        // n is old count; fl->count is new count
    
        fl->flight = realloc(fl->flight,
            (fl->count + 1) * sizeof(*fl->flight));
    
        snprintf(fl->flight[n].airline, 4, "%s", airline);
        snprintf(fl->flight[n].dest, 4, "%s", dest);
        fl->flight[n].number = number;
    }
    

    Intialize the flights struct with NULL and a count of zero and don't forget to release the used memory when you're done:

    int main(void)
    {
        Flights fl = {NULL, 0};
    
        add_flight(&fl, "AF", 5512, "CDG");
        add_flight(&fl, "AA", 1100, "ATL");
        add_flight(&fl, "LH", 6537, "FRA");
        add_flight(&fl, "BA", 8821, "LHR");
        add_flight(&fl, "IB", 1081, "EZE");
    
        print_flights(&fl);
    
        free(fl.flight);
    
        return 0;
    }
    

    You can see it in action here. Some observations:

    • There is no need to distinguish between adding the first and subsequent flights, because realloc(NULL, size) behaves exactly like malloc(size).
    • It is not very efficient to reallocate the memory for each added item. Instead, you pick a suitable initial array size like 4 or 8, then double the size when you hit the limit. That means that the allocated size and the count may differ and you need an aditional memsize field in your flights struct.
    • The code above relies on manual initialization and destruction. Usually, you will write "constructor" and "destructor" functions to do that for you.