Search code examples
cstringmallocrealloc

Why does realloc fail on repeated calls where as allocating a big chunk using malloc works?


I am trying to read in from stdin (passing in value from a file). I am reading each character from the string and storing it into a dynamically allocated string pointer. When needed I realloc the memory. I am trying to get as many characters as possible. Though I can limit it to 100,000 chars. But the realloc fails after some iteration. But if I specify a chunk size big, say 1048567 during the first initialization in malloc, I am able to read the string completely. Why is this?

Below is my program:

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

int display_mem_alloc_error();

enum {
    CHUNK_SIZE = 31    //31 fails. But 1048567 passes.
};

int display_mem_alloc_error() {
    fprintf(stderr, "\nError allocating memory");
    exit(1);
}

int main(int argc, char **argv) {
    int numStr;                  //number of input strings
    int curSize = CHUNK_SIZE;    //currently allocated chunk size
    int i = 0;                   //counter
    int len = 0;                 //length of the current string
    int c;                       //will contain a character
    char *str = NULL;            //will contain the input string
    char *str_cp = NULL;         //will point to str
    char *str_tmp = NULL;        //used for realloc

    str = malloc(sizeof(*str) * CHUNK_SIZE);
    if (str == NULL) {
        display_mem_alloc_error();
    }    
    str_cp = str;   //store the reference to the allocated memory

    scanf("%d\n", &numStr);   //get the number of input strings
    while (i != numStr) {
        if (i >= 1) {   //reset
            str = str_cp;
            len = 0;
            curSize = CHUNK_SIZE;
        }
        c = getchar();
        while (c != '\n' && c != '\r') {
            *str = (char *) c;
            //printf("\nlen: %d -> *str: %c", len, *str);
            str = str + 1;
            len = len + 1;
            *str = '\0';
            c = getchar();
            if (curSize / len == 1) {
                curSize = curSize + CHUNK_SIZE;
                //printf("\nlen: %d", len);
                printf("\n%d \n", curSize);    //NB: If I comment this then the program simply exits. No message is displayed.
                str_tmp = realloc(str_cp, sizeof(*str_cp) * curSize);
                if (str_tmp == NULL) {
                    display_mem_alloc_error();
                }
                //printf("\nstr_tmp: %d", str_tmp);
                //printf("\nstr: %d", str);
                //printf("\nstr_cp: %d\n", str_cp);
                str_cp = str_tmp;
                str_tmp = NULL;
            }
        }
        i = i + 1;
        printf("\nlen: %d", len);
        //printf("\nEntered string: %s\n", str_cp);
    }
    str = str_cp;
    free(str_cp);
    free(str);
    str_cp = NULL;
    str = NULL;
    return 0;
}

Thanks.


Solution

  • When you realloc

    str_tmp = realloc(str_cp, sizeof(*str_cp) * curSize);
    if (str_tmp == NULL) {
        display_mem_alloc_error();
    }
    //printf("\nstr_tmp: %d", str_tmp);
    //printf("\nstr: %d", str);
    //printf("\nstr_cp: %d\n", str_cp);
    str_cp = str_tmp;
    str_tmp = NULL;
    

    you let str_cp point to the new block of memory, but str still points into the old, now freed block. Thus when you access what str points to in the next iteration, you invoke undefined behaviour.

    You need to save the offset of str with respect to str_cp, and after the reallocation, letstr point into the new block at its old offset.

    And *str = (char *) c; is wrong, although there is a nonzero chance of it being functionally equivalent to the correct *str = c;.