0

I have a program that only does two things for now. Read data from two files, into two arrays as structs, and then freeing these arrays. These stucts have strings in them, so I malloc() memory for them manually. Everything was working, I could print everything correctly, so I wrote the free()ing methods for these arrays, which were working as well. I also prepared the program to be able to read more data, if the files were bigger, and that's where the problems came. I keep track of the capacity and the actual size, and if size is at capacity, I simply realloc() the array and continue reading. I check if the realloc() was successful, and if not, the program exits. I tried to test it, but it seems that I have done something wrong and I cannot figure out what happens.
If I need to realloc() valgrind reports errors, leaks, and eventually the program exits with Segmentation fault. It seems if I have enough initial capacity there aren't any problems, no leaks, no errors, no Segmentation fault.
As problems only emerge after realloc() I naturally tried to research the use of realloc(), but I did not find what can go wrong. I cannot see why this could happen.

Here is the code:
menu.c

#define BUFSIZE 1024
#define INITCAP 8

void runMenu()
{
    int addressesSize = 0;
    int applicationsSize = 0;
    Address *addresses = malloc(INITCAP * sizeof(Address));
    Application *applications = malloc(INITCAP * sizeof(Application));

    initializeAddresses(addresses, &addressesSize);
    initializeData(applications, &applicationsSize);

    freeAddresses(addresses, addressesSize);
    freeApplications(applications, applicationsSize);
}

void initializeAddresses(Address *addresses, int *size)
{
    int actCap = INITCAP;
    char buffer[BUFSIZE];

    FILE *input = fopen("addresses.txt", "r");

    if (input == NULL)
    {
        IOError("addresses.txt");
    }

    if (addresses == NULL)
    {
        nomem();
    }

    while (fgets(buffer, BUFSIZE, input))
    {
        if (*size == actCap)
        {
            actCap *= 2;
            addresses = realloc(addresses, actCap * sizeof(*addresses));
            if (addresses == NULL)
            {
                nomem();
            }
        }

        int len = strlen(buffer);

        if (isspace(buffer[len - 1]))
        {
            buffer[len - 1] = '\0';
        }

        Address a;
        a.name = malloc((len + 1) * sizeof(char));
        if (a.name == NULL)
        {
            nomem();
        }
        a.id = *size;
        strcpy(a.name, buffer);
        addresses[(*size)++] = a;
    }
    fclose(input);
}

void initializeData(Application *applications, int *size)
{
    int actCap = INITCAP;
    char buffer[BUFSIZE];

    FILE *input = fopen("data.txt", "r");

    if (input == NULL)
    {
        IOError("data.txt");
    }

    if (applications == NULL)
    {
        nomem();
    }

    printf("starting read from data.txt\n");
    while (fgets(buffer, BUFSIZE, input))
    {
        printf("size is %d\n", *size);
        if (*size == actCap)
        {
            printf("size is at capacity: %d\n", actCap);
            actCap *= 2;
            printf("capacity now: %d\nreallocing\n", actCap);
            applications = realloc(applications, actCap * sizeof(*applications));
            if (applications == NULL)
            {
                nomem();
            }
            printf("realloced successfully\n");
        }

        Application a;

        char *nameBuf = strtok(buffer, ";");
        a.name = malloc((strlen(nameBuf) + 1) * sizeof(char));
        strcpy(a.name, nameBuf);

        char *addressBuf = strtok(0, ";");
        a.address = malloc((strlen(addressBuf) + 1) * sizeof(char));
        strcpy(a.address, addressBuf);

        char *strNumOfApplicationBuf = strtok(0, ";");
        int numOfApplication = atoi(strNumOfApplicationBuf);
        if (numOfApplication <= 0)
        {
            formatError("Az egyik jelentkezőhöz a jelentkezésének száma helytelenül van megadva");
        }
        a.numOfApplication = numOfApplication;

        if ((strtok(0, ";")) != NULL)
        {
            formatError("A jelentkezéseket tartalmazó fájl több oszlopot tartlamaz a megengedettnél");
        }

        a.id = *size;
        applications[(*size)++] = a;
    }
    fclose(input);
}

void freeAddresses(Address *list, int size)
{
    for (int i = 0; i < size; ++i)
    {
        free(list[i].name);
    }
    free(list);
}

void freeApplications(Application *list, int size)
{
    printf("\nthere are %d applications\n", size);
    for (int i = 0; i < size; i++)
    {
        printf("freeing: %d: name = %s\n", i, list[i].name);
        free(list[i].name);
        free(list[i].address);
    }
    free(list);
}

void nomem()
{
    fprintf(stderr, "Nincs memória\n");
    exit(1);
}

void IOError(char *fileName)
{
    fprintf(stderr, "Nem sikerült megnyitni egy szükséges fájlt: %s\n", fileName);
    exit(1);
}

void formatError(char *what)
{
    fprintf(stderr, "Formátum hiba: %s\n", what);
    exit(1);
}

Address

typedef struct Address
{
    char* name;
    int id;
} Address;

Application

typedef struct Application
{
    char* name;
    char* address;
    int numOfApplication;
    int id;
} Application;

I have a main.c which for now only calls runMenu(). If I increase INITCAP to say 80, which is more than needed, this is the output from valgrind reports 0 errors and no leaks possible.

If I reduce it back to 8 this is the output:

==44== Memcheck, a memory error detector
==44== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==44== Using Valgrind-3.16.1 and LibVEX; rerun with -h for copyright info
==44== Command: ./a.out
==44== 
starting read from data.txt
size is 0
size is 1
size is 2
size is 3
size is 4
size is 5
size is 6
size is 7
size is 8
size is at capacity: 8
capacity now: 16
reallocing
realloced successfully
size is 9
size is 10
size is 11
size is 12

there are 13 applications
==44== Invalid read of size 8
==44==    at 0x1097C0: freeApplications (in a.out)
==44==    by 0x1092B3: runMenu (in a.out)
==44==    by 0x109232: main (in a.out)
==44==  Address 0x4a16100 is 0 bytes inside a block of size 192 free'd
==44==    at 0x483AD7B: realloc (vg_replace_malloc.c:834)
==44==    by 0x109563: initializeData (in a.out)
==44==    by 0x109291: runMenu (in a.out)
==44==    by 0x109232: main (in a.out)
==44==  Block was alloc'd at
==44==    at 0x483877F: malloc (vg_replace_malloc.c:307)
==44==    by 0x109267: runMenu (in a.out)
==44==    by 0x109232: main (in a.out)
==44== 
freeing: 0: name = Tapsi Hapsi
==44== Invalid read of size 8
==44==    at 0x1097F6: freeApplications (in a.out)
==44==    by 0x1092B3: runMenu (in a.out)
==44==    by 0x109232: main (in a.out)
==44==  Address 0x4a16100 is 0 bytes inside a block of size 192 free'd
==44==    at 0x483AD7B: realloc (vg_replace_malloc.c:834)
==44==    by 0x109563: initializeData (in a.out)
==44==    by 0x109291: runMenu (in a.out)
==44==    by 0x109232: main (in a.out)
==44==  Block was alloc'd at
==44==    at 0x483877F: malloc (vg_replace_malloc.c:307)
==44==    by 0x109267: runMenu (in a.out)
==44==    by 0x109232: main (in a.out)
==44==
==44== Invalid read of size 8
==44==    at 0x10981E: freeApplications (in a.out)       
==44==    by 0x1092B3: runMenu (in a.out)
==44==    by 0x109232: main (in a.out)
==44==  Address 0x4a16108 is 8 bytes inside a block of size 192 free'd
==44==    at 0x483AD7B: realloc (vg_replace_malloc.c:834)
==44==    by 0x109563: initializeData (in a.out)
==44==    by 0x109291: runMenu (in a.out)
==44==    by 0x109232: main (in a.out)
==44==  Block was alloc'd at
==44==    at 0x483877F: malloc (vg_replace_malloc.c:307)
==44==    by 0x109267: runMenu (in a.out)
==44==    by 0x109232: main (in a.out)
==44==
freeing: 1: name = Nyúl Péter
freeing: 2: name = Nyuszi Dani
freeing: 3: name = Nyuszó Lóránt
freeing: 4: name = Nyuszika Marci
freeing: 5: name = Nyúl Endre
freeing: 6: name = Tapsi Márton
freeing: 7: name = Tapsi Csongor
freeing: 8: name = (null)
==44== Invalid read of size 1
==44==    at 0x483BC82: strlen (vg_replace_strmem.c:459)
==44==    by 0x48B9F75: __vfprintf_internal (vfprintf-internal.c:1688)
==44==    by 0x48A4D9A: printf (printf.c:33)
==44==    by 0x1097D8: freeApplications (in a.out)       
==44==    by 0x1092B3: runMenu (in a.out)
==44==    by 0x109232: main (in a.out)
==44==  Address 0x100 is not stack'd, malloc'd or (recently) free'd
==44==
==44==
==44== Process terminating with default action of signal 11 (SIGSEGV)
==44==  Access not within mapped region at address 0x100
==44==    at 0x483BC82: strlen (vg_replace_strmem.c:459)
==44==    by 0x48B9F75: __vfprintf_internal (vfprintf-internal.c:1688)
==44==    by 0x48A4D9A: printf (printf.c:33)
==44==    by 0x1097D8: freeApplications (in a.out)       
==44==    by 0x1092B3: runMenu (in a.out)
==44==    by 0x109232: main (in a.out)
==44==  If you believe this happened as a result of a stack
==44==  overflow in your program's main thread (unlikely but
==44==  possible), you can try to increase the size of the
==44==  main thread stack using the --main-stacksize= flag.
==44==  The main thread stack size used in this run was 8388608.
freeing: 9: name = ==44== 
==44== HEAP SUMMARY:
==44==     in use at exit: 508 bytes in 11 blocks
==44==   total heap usage: 41 allocs, 30 frees, 4,089 bytes allocated
==44==
==44== 508 (384 direct, 124 indirect) bytes in 1 blocks are definitely lost in loss record 3 of 3
==44==    at 0x483AD7B: realloc (vg_replace_malloc.c:834)
==44==    by 0x109563: initializeData (in a.out)
==44==    by 0x109291: runMenu (in a.out)
==44==    by 0x109232: main (in a.out)
==44==
==44== LEAK SUMMARY:
==44==    definitely lost: 384 bytes in 1 blocks
==44==    indirectly lost: 124 bytes in 10 blocks
==44==      possibly lost: 0 bytes in 0 blocks
==44==    still reachable: 0 bytes in 0 blocks
==44==         suppressed: 0 bytes in 0 blocks
==44==
==44== For lists of detected and suppressed errors, rerun with: -s
==44== ERROR SUMMARY: 30 errors from 5 contexts (suppressed: 0 from 0)
Segmentation fault

note: I replaced the real path to just a.out

  • 1
    `applications = ....` means *nothing* to the caller of those functions. In C, parameters are passed by value. There are (at least) a few hundred duplicates of this problem, albeit difficult to pin down in searching because it is a mistake generally made by programmers new to the C language, and as such the choice of vernacular in describing what is going on i so bloody divergent searching becomes near-worthless. I'll peck around. [Here is one](https://stackoverflow.com/questions/5531765/change-pointer-passed-by-value). – WhozCraig Mar 25 '22 at 08:22
  • The memory leak, at least, comes from incorrect usage of `realloc` – if reallocation fails, the old memory is left intact, as is. But you overwrite your only pointer to this memory by assigning a null pointer to it. Correct is the following: `tmp = realloc(...); if(tmp) { addresses = tmp; } else { /*whatever appropriate error handling */ }`. – Aconcagua Mar 25 '22 at 08:28
  • @WhozCraig We made a community wiki FAQ for this common bug, see the linked duplicate. You can find a decent collection of duplicate targets in the [C tag wiki](https://stackoverflow.com/tags/c/info), scroll down to FAQ - and then in this case find a dupe target below "dynamic memory allocation". – Lundin Mar 25 '22 at 08:35
  • It's still not clear for me why would this happen only if I `realloc()`. Also I tried to follow the linked answer and now things are even worse – szabolcstarnai Mar 25 '22 at 08:55
  • Okay I followed the answer but introduced a new bug, but I resolved it. – szabolcstarnai Mar 25 '22 at 09:14
  • Also I now understand why everything happened, thank you. – szabolcstarnai Mar 25 '22 at 09:40

0 Answers0