Search code examples
cmallocvalgrindrealloc

realloc() C-language change value in int array


I'm trying to use realloc() every loop, so I only use necessary memory for my int array in C, but the output values are changed. Nevertheless, when using Valgrind on my code, I have the right values.

I'm doing the first day of Advent of Code 2022.

The input file is a .txt file that looks like this:

7569
1357
10134
4696
4423
8869
3562
6597

4038
9038
1352
8005
4811
6281
3961
4023

7234
3510
7728
1569
4583
7495
3941
6015
6531
2637

I was trying to sum numbers and store it in my array in a specific index, and if there is a blank line increase my index.

Given that example input, it should print like this:

elf [0] = 47207
elf [1] = 41509 
elf [2] = 51243

What I got:

elf [245] = 63138
elf [246] = 181168
elf [247] = 41570
elf [248] = 36264
elf [249] = 59089
elf [250] = 185061

What I want (result using valgrind):

elf [245] = 63138
elf [246] = 52399
elf [247] = 41570
elf [248] = 36264
elf [249] = 59089
elf [250] = 56308

My code:

int *read_calories(char *filename)
{
    FILE *fp = fopen(filename, "r");
    char *line = NULL;
    int i = 0;
    size_t len = 0;
    ssize_t nread;
    struct stat size;
    stat(filename, &size);
    int tab_size = 1;
    int *calories = malloc(sizeof(int) * 2);

    if (fp == NULL)
    {
        perror("Can't open file\n");
        exit(EXIT_FAILURE);
    }

    while ((nread = getline(&line, &len, fp)) != -1) 
    {
        if (nread == 1) {
            i++;
            ++tab_size;
            calories = realloc(calories, tab_size * sizeof(int));
        } else {
            calories[i] += atoi(line);
        }
    }
    calories[i + 1] = '\0';
    free(line);
    fclose(fp);

    return calories;
}

int main()
{
    int *calories = read_calories("input.txt");
    for (int i = 0; calories[i] != '\0'; i++) {
        printf("elf [%d] = %d \n", i, calories[i]);
    }
    free(calories);
    return 0;
}

Solution

  • Your calorie reading code has good stuff in it, but is rather disorganized. The data allocated by malloc() is not zeroed, so using += in calories[i] += atoi(line); is not good. You've not shown the input data format.

    It isn't clear whether you have to read a bunch of numbers up to a blank line and store the sum in the array (and rinse and repeat to EOF), or whether you just need to read numbers from the file and store those into the array.

    Each line has a number to be stored separately

    The code below assumes that each line contains a number that should be stored in the array. Adapting to the other style of processing is not difficult.

    #include <assert.h>
    #include <errno.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    extern int *read_calories(const char *filename);
    
    int *read_calories(const char *filename)
    {
        FILE *fp = fopen(filename, "r");
        if (fp == NULL)
        {
            fprintf(stderr, "Failed to open file '%s' for reading (%d: %s)\n", filename, errno, strerror(errno));
            exit(EXIT_FAILURE);
        }
    
        int tab_used = 0;
        int tab_size = 2;
        int *calories = malloc(sizeof(int) * tab_size);
        if (calories == NULL)
        {
            fprintf(stderr, "Failed to allocate memory (%d: %s)\n", errno, strerror(errno));
            exit(EXIT_FAILURE);
        }
    
        char *line = NULL;
        size_t len = 0;
        while (getline(&line, &len, fp) != -1) 
        {
            if (tab_used == tab_size - 1)
            {
                size_t new_size = 2 * tab_size;
                void  *new_data = realloc(calories, new_size * sizeof(int));
                if (new_data == NULL)
                {
                    fprintf(stderr, "Failed to allocate memory (%d: %s)\n", errno, strerror(errno));
                    exit(EXIT_FAILURE);
                }
                calories = new_data;
                tab_size = new_size;
            }
            calories[tab_used++] = atoi(line);
        }
        calories[tab_used] = 0;
        free(line);
        fclose(fp);
    
        return calories;
    }
    
    int main(void)
    {
        int *calories = read_calories("input.txt");
        assert(calories != NULL);
        for (int i = 0; calories[i] != 0; i++)
            printf("elf [%d] = %d \n", i, calories[i]);
        free(calories);
        return 0;
    }
    

    I'm not keen on perror() — it does a job and is simple, but it's relatively hard to get good messages out of it. The code ensures there's an extra entry in the array for a zero entry at the end. It does not, however, spot a zero entry in the middle of the array. That would usually be caused by atoi() failing to convert the value.

    I generated an input.txt file containing 10 random values between 100 and 1000:

    478
    459
    499
    997
    237
    423
    185
    630
    964
    594
    

    The output from the program was:

    elf [0] = 478 
    elf [1] = 459 
    elf [2] = 499 
    elf [3] = 997 
    elf [4] = 237 
    elf [5] = 423 
    elf [6] = 185 
    elf [7] = 630 
    elf [8] = 964 
    elf [9] = 594 
    

    Blocks of numbers to be summed, separated by blank lines

    This code is closely based on the previous answer, but the 'add to the array' code is extracted into a function so it can be used twice. It might be better to use a structure to encapsulate the array details. I should probably also use size_t rather than int for the sizes.

    #include <assert.h>
    #include <errno.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    static void add_to_array(int **table, int *tab_size, int *tab_used, int value)
    {
        if (*tab_used == *tab_size - 1)
        {
            size_t new_size = 2 * *tab_size;
            void  *new_data = realloc(*table, new_size * sizeof(int));
            if (new_data == NULL)
            {
                fprintf(stderr, "Failed to allocate memory (%d: %s)\n", errno, strerror(errno));
                exit(EXIT_FAILURE);
            }
            *table = new_data;
            *tab_size = new_size;
        }
        (*table)[(*tab_used)++] = value;
    }
    
    static int *read_calories(const char *filename)
    {
        FILE *fp = fopen(filename, "r");
        if (fp == NULL)
        {
            fprintf(stderr, "Failed to open file '%s' for reading (%d: %s)\n", filename, errno, strerror(errno));
            exit(EXIT_FAILURE);
        }
    
        int tab_used = 0;
        int tab_size = 2;
        int *calories = malloc(sizeof(int) * tab_size);
        if (calories == NULL)
        {
            fprintf(stderr, "Failed to allocate memory (%d: %s)\n", errno, strerror(errno));
            exit(EXIT_FAILURE);
        }
    
        char *line = NULL;
        size_t len = 0;
        int current_sum = 0;
        ssize_t nread;
        while ((nread = getline(&line, &len, fp)) != -1) 
        {
            if (nread == 1)
            {
                add_to_array(&calories, &tab_size, &tab_used, current_sum);
                current_sum = 0;
            }
            else
                current_sum += atoi(line);
        }
        if (current_sum > 0)
            add_to_array(&calories, &tab_size, &tab_used, current_sum);
        calories[tab_used] = 0;
        free(line);
        fclose(fp);
    
        return calories;
    }
    
    int main(void)
    {
        int *calories = read_calories("input.txt");
        assert(calories != NULL);
        for (int i = 0; calories[i] != 0; i++)
            printf("elf [%d] = %d \n", i, calories[i]);
        free(calories);
        return 0;
    }
    

    Revised data file:

    184
    861
    513
    507
    790
    
    897
    715
    287
    729
    534
    777
    945
    
    950
    696
    605
    
    287
    763
    839
    860
    779
    
    522
    140
    281
    190
    744
    976
    
    420
    462
    591
    710
    435
    707
    580
    855
    208
    
    806
    205
    799
    
    537
    395
    
    922
    356
    397
    464
    435
    470
    973
    
    203
    713
    264
    

    (Note that there isn't a blank line at the end!)

    Output:

    elf [0] = 2855 
    elf [1] = 4884 
    elf [2] = 2251 
    elf [3] = 3528 
    elf [4] = 2853 
    elf [5] = 4968 
    elf [6] = 1810 
    elf [7] = 932 
    elf [8] = 4017 
    elf [9] = 1180 
    

    Awk script to cross-check the result:

    awk 'NF == 0 { print sum; sum = 0 } NF == 1 { sum += $1 } END { print sum }' input.txt
    

    Results:

    2855
    4884
    2251
    3528
    2853
    4968
    1810
    932
    4017
    1180