0

I am currently taking a procedural programming course at my school. We are using C with C99 standard. I discussed this with my instructor and I cannot understand why realloc() is working for his machine, but it is not working for mine.

The goal of this program is to parse a text file students.txt that has students' name and their GPA formatted like this:

Mary 4.0
Jack 2.45
John 3.9
Jane 3.8
Mike 3.125

I have a function that resizes my dynamically allocated array, and when I use realloc the debugger in my CLion IDE, it gave me SIGABRT.

I tried using an online compiler and I get realloc(): invalid next size.

I have been trying to debug this all weekend and I can't find the answer and I need help.

My code is currently looking like this

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

#define INITIAL_SIZE 4
#define BUFFER_SIZE 512
#define GRADE_CUTOFF 3.9

// ERROR CODES
#define FILE_OPEN_ERROR 1
#define MEMORY_ALLOCATION_ERROR 2

struct student {
    double gpa;
    char *name;
};

struct student *resizeAllocationIfNeeded(struct student *listOfStudents,
        unsigned int studentCount, size_t *currentSize) {

    if (studentCount <= *currentSize) {
        return listOfStudents;
    }

    *currentSize *= 2;
    struct student *resizedList = (struct student *) realloc(listOfStudents, *currentSize * sizeof(struct student));
    if (resizedList == NULL) {
        perror("Failed to allocate memory");
        exit(MEMORY_ALLOCATION_ERROR);
    }
    return resizedList;
}

size_t getNamesAndGrades(FILE *file, struct student *listOfStudents, size_t size) {
    unsigned int studentCount = 0;
    char buffer[BUFFER_SIZE];

    while(fscanf(file, "%s %lf", buffer, &listOfStudents[studentCount].gpa) > 0) {
        listOfStudents[studentCount].name = strdup(buffer);
        studentCount++;
        listOfStudents = resizeAllocationIfNeeded(listOfStudents, studentCount, &size);
    }

    return studentCount;
}

void swapStudents(struct student *listOfStudents, int x, int y) {
    struct student temp = listOfStudents[x];
    listOfStudents[x] = listOfStudents[y];
    listOfStudents[y] = temp;
}

void sortStudentsByGPA(struct student *listOfStudents, unsigned int studentCount) {
    for (int i = 0; i < studentCount; i++) {
        for (int j = 0; j < studentCount - i - 1; j++) {
            if (listOfStudents[j].gpa < listOfStudents[j + 1].gpa) {
                swapStudents(listOfStudents, j, j + 1);
            }
        }
    }
}

void printStudentAndGPA(struct student *listOfStudents, unsigned int studentCount) {
    for (int i = 0; i < studentCount; i++) {
        if (listOfStudents[i].gpa > GRADE_CUTOFF) {
            printf("%s %lf\n", listOfStudents[i].name, listOfStudents[i].gpa);
        }
        free(listOfStudents[i].name);
    }
}

void topStudents(char *fileName) {
    FILE *file = fopen(fileName, "r");

    if (!file) {
        perror("Could not open file for reading");
        exit(FILE_OPEN_ERROR);
    }

    struct student *listOfStudents = (struct student *) malloc(INITIAL_SIZE * sizeof(struct student));

    if (listOfStudents == NULL) {
        perror("Failed to allocate memory");
        exit(MEMORY_ALLOCATION_ERROR);
    }

    unsigned int studentCount = getNamesAndGrades(file, listOfStudents, INITIAL_SIZE);
    sortStudentsByGPA(listOfStudents, studentCount);
    printStudentAndGPA(listOfStudents, studentCount);
    free(listOfStudents);
}

int main() {
    topStudents("students.txt");
    return 0;
}

Marco Bonelli
  • 63,369
  • 21
  • 118
  • 128
Ryan Leung
  • 11
  • 3
  • 2
    Run your code through valgrind. If you're mismanaging memory, it will tell you where. – dbush Nov 03 '20 at 03:10
  • You either need to resize the array **before** adding the student, or change `if (studentCount <= *currentSize)` to `if (studentCount < *currentSize)` in `resizeAllocationIfNeeded` – user3386109 Nov 03 '20 at 03:11
  • Thanks for taking the time to have a look at my code. After changing the condition check, it is still giving me the same behavior. I will have a look on it through Valgrind in the meantime. – Ryan Leung Nov 03 '20 at 03:21

2 Answers2

2

You have a fencepost error when checking whether you need to resize the array.

Your initial allocation size is 4, which means that the highest valid index is 3.

In the loop in getNamesAndGrades(), after you read into listOfStudents[3] you increment studentCount to 4. Then you call resizeAllocationIfNeeded(listOfStudents, studentCount, &size);

Inside resizeAllocationIfNeeded(), studentCount == 4 and *currentSize == 4. So the test

    if (studentCount <= *currentSize) {
        return listOfStudents;
    }

succeeds and you return without calling realloc().

Then the next iteration of the loop assigns to listOfStudents[4], which causes a buffer overflow.

You need to change that condition to studentCount < *currentSize.

Barmar
  • 741,623
  • 53
  • 500
  • 612
  • Thanks for taking the time to have a look at my code. After changing the condition check, it is still giving me the same behavior. – Ryan Leung Nov 03 '20 at 03:19
  • I don't see any other obvious errors. I suggest you use valgrind as someone else suggested. – Barmar Nov 03 '20 at 03:27
1

There are two errors in your code: one is just a typo, the other is a more serious logical error.

First, you are reallocating too late, because of the condition in resizeAllocationIfNeeded(). When studentCount == currentSize, this doesn't resize (even though it should), which makes you overflow the array of students and causes problems.

You can change the condition to fix this:

if (studentCount < *currentSize) {
    return listOfStudents;
}

Apart from the above, your main error is in getNamesAndGrades(), where you are reallocating memory and assigning the new pointers to a local variable. You then use that variable in topStudents() as if it was updated. This will of course not work, as the initial pointer passed by topStudents() becomes invalid after the first realloc() and memory is irrevocably lost when getNamesAndGrades() returns.

You should either pass a pointer to the student array, or better just make the function create the array for you.

Here's a solution, renaming getNamesAndGrades to getStudents:

struct student *getStudents(FILE *file, unsigned int *studentCount) {
    char buffer[BUFFER_SIZE];
    struct student *listOfStudents;
    size_t size = INITIAL_SIZE;

    *studentCount = 0;
    listOfStudents = malloc(size * sizeof(struct student));
    
    if (listOfStudents == NULL) {
        perror("Failed to allocate memory");
        exit(MEMORY_ALLOCATION_ERROR);
    }

    while(fscanf(file, "%511s %lf", buffer, &listOfStudents[*studentCount].gpa) == 2) {
        listOfStudents[*studentCount].name = strdup(buffer);
        (*studentCount)++;
        listOfStudents = resizeAllocationIfNeeded(listOfStudents, *studentCount, &size);
    }

    return listOfStudents;
}

// ...

void topStudents(char *fileName) {
    FILE *file = fopen(fileName, "r");

    if (!file) {
        perror("Could not open file for reading");
        exit(FILE_OPEN_ERROR);
    }

    unsigned int studentCount;
    struct student *listOfStudents = getStudents(file, &studentCount);

    sortStudentsByGPA(listOfStudents, studentCount);
    printStudentAndGPA(listOfStudents, studentCount);
    free(listOfStudents);
}

int main() {
    topStudents("students.txt");
    return 0;
}

Additional notes:

  • When scanning on a fixed size buffer (in this case 512 bytes), use %511s, not just %s, that's a buffer overflow waiting to happen.
  • You are scanning two fields, so check if fscanf's return value is == 2, not > 0, you don't want for example one field initialized and one not.
  • Don't cast the result of malloc() or realloc()
  • For the future, if you are on Linux, compiling with gcc -g -fsanitize=address will give you detailed error reports when something goes bad in the heap, telling you exactly where memory was allocated, freed and used.
Marco Bonelli
  • 63,369
  • 21
  • 118
  • 128
  • Wow, that was hard to spot. I did not even think about the fact that I need to pass the reference to my malloc array because I am using it in two levels of function calls. Your solution makes much more sense as the code is easier to follow. Thank you so much! – Ryan Leung Nov 03 '20 at 03:52