lowkeyhuman
lowkeyhuman

Reputation: 35

Struct array realloc in function - produces undefined behaviour

I have a struct which holds 3 char pointers (arbitrary length strings) and I created a dynamic array with malloc since It can have an arbitrary amount of entries. This is my struct data type:

typedef struct student{
    char* name;
    char* phoneNumber;
    char* department;
}STUDENT;

I used a function to realloc to increase the size of the struct array whenever a new entry is needed and another function to print the whole array.

The problem that i encounter is that: I am able to print the new entry inside the addNumber function but it is not always the case when I tried printing outside the addNumber function. Sometimes it works, most of the time I got a segmentation fault: 11 and one time while adding a new entry I got a 'malloc: *** error for object 0x7fc426c058f0: pointer being realloc'd was not allocated'

Here is inside of my main:

int nEntry = 0;
STUDENT* directory = malloc(nEntry * sizeof *directory);
if(directory == NULL){
    puts("Unable to allocate memory");
    exit(-1);
}
while(1){
    int choice = 0;
    scanf("%d", &choice);
    while(getchar() != '\n');
    switch(choice){
        case 1:
            printDirectory(directory, nEntry);
            break;
        case 2:
            addNumber(directory, &nEntry);
            break;
        default:
            printf("Unknown option!\n");
            break;
    }
}
return 0;

Here is my addNumber function:

void addNumber(STUDENT* Array, int* nArray){
    *nArray += 1;
    int x = *nArray - 1;
    STUDENT* tempDirectory = realloc(Array, *nArray * sizeof *Array);
    if(tempDirectory == NULL){
        puts("Unable to allocate memory");
        exit(-1);
    }
    else{
        Array = tempDirectory;
        Array[x].name = (char*)malloc(sizeof(char*));
        Array[x].phoneNumber = (char*)malloc(sizeof(char*));
        Array[x].department = (char*)malloc(sizeof(char*));

        printf("Name: ");
        scanf("%[^\n]", Array[x].name);
        while(getchar() != '\n');

        printf("Number: ");
        scanf("%[^\n]", Array[x].phoneNumber);
        while(getchar() != '\n');

        printf("Department: ");
        scanf("%[^\n]", Array[x].department);
        while(getchar() != '\n');

        for(int i = 0; i < *nArray; i++){
        printf("%s\t%s\t(%s)\n", (Array + i)->name, (Array + i)->phoneNumber, (Array + i)->department);
        }
    }
}

and Here is my print function:

void printDirectory(STUDENT* Array, int nArray){
    int i;
    for(i = 0; i < nArray; i++){
        printf("%s\t%s\t(%s)\n", (Array + i)->name, (Array + i)->phoneNumber, (Array + i)->department);
    }
}

The print function works fine if I hardcode the entry into main, the problem seems to be that whats created in the addNumber function isn't properly passed back? But then for all the arguments i am passing by reference, I am confused why I'm getting an undefined behaviour.

Upvotes: 0

Views: 119

Answers (2)

EdmCoff
EdmCoff

Reputation: 3596

As pointed out in comments, your mallocs are the wrong size, so there are other problems with your code, but to address your specific concern about realloc:

To store the return value of realloc so it can be accessed in your main function, you're going to need to pass the address of the pointer.

So in main:

addNumber(&directory, &nEntry); // Note the ampersand

And in addNumber:

void addNumber(STUDENT** Array, int* nArray){ // Note the pointer to a pointer
...
STUDENT* tempDirectory = realloc(*Array, *nArray * sizeof *Array); // Note Array is dereferenced
...
*Array = tempDirectory;
...

If you picture directory in main as a variable stored at location 0x1 that points to memory location 0x2, in your current code you are sending 0x2 to the function. The function can read the data there, but it has no reference to 0x1 to update the directory variable in main.

The general rule for passing by reference is: If you want to update the reference in a function so that the caller can read it, you need to pass it with one more asterix than the variable has in the caller. So if you have an int in main, you would want to update an int* in the function. If you have an int*** in main, you would want to update an int****.

Upvotes: 2

Vlad from Moscow
Vlad from Moscow

Reputation: 311156

For starters this code snippet

int nEntry = 0;
STUDENT* directory = malloc(nEntry * sizeof *directory);
if(directory == NULL){
    puts("Unable to allocate memory");
    exit(-1);
}

does not make a sense. The behavior of a call of malloc with the argument equal to 0 is implementation defined. That is such a call can return either NULL or some valid pointer depending on the used system.

It will be enough to write

int nEntry = 0;
STUDENT* directory = NULL;

The function addNumber deals with a copy of the value of the passed argument. So changing the copy within the function does not influence on the original argument.

You have to pass the original argument by reference. That is through pointer to pointer.

void addNumber( STUDENT **Array, int *nArray );
                        ^^^^^^^

Also the similar parameter names like Array and nArray make function code unreadable.

The function could be declared the following way

int addNumber( STUDENT **directory, int *n )
{
    STUDENT *tempDirectory = realloc( *directory, ( *n + 1 ) * sizeof **directory );
    if ( !tempDirectory ) return 0;

    ++*n;
    *directory = tempDirectory;

    // and so on

    return 1;
}

and the caller could check the return value of the function whether it ends with success or failure and issue a corresponding message if required.

These memory allocations

    Array[x].name = (char*)malloc(sizeof(char*));
    Array[x].phoneNumber = (char*)malloc(sizeof(char*));
    Array[x].department = (char*)malloc(sizeof(char*));

also do not make a sense. You need to allocate character arrays where you are going to store inputted strings in calls like this

scanf("%[^\n]", Array[x].name);

You could declare an auxiliary character array like for example

char s[100];

and read strings in this arrays. For example

    printf("Name: ");
    fgets( s, sizeof( s ), stdin );
    s[ strcspn( s, "\n" ) ] = '\0'; 

Then you could allocate a memory for the data member name knowing the length of the read string like

( *Array )[x].name = malloc( strlen( s ) + 1 );

and copy the string

strcpy( ( *Array )[x].name, s );

Upvotes: 2

Related Questions