Search code examples
carraysstringrealloc

realloc fails to expand char array when piping input from a file


I wrote the below c function to get a string from a user. It uses realloc to dynamically change the char array size to accommodate for unknown char array length. From my understanding, it should be able to take as much input as you can throw at it (or have memory available), however, when I attempt to pipe text to it from a randomized text file (used "tr '\n' ' ' ./random.txt" to ensure I removed any newlines from the text file), I get the "Unable to allocate memory to hold char array. Exiting!" error message. Why is this occurring? Should my array be able to hold up to Gigabytes of data since I have 16 Gigabytes of RAM the way it was designed to dynamically grow?

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

void GetString(int*, int*);

int main(void)
{
    unsigned int strLength = 32;
    char *stringPtr = malloc(strLength);
    if (stringPtr == NULL)
    {
        fprintf(stderr, "Unable to allocate memory to hold char array. Exiting!\n");
        return 1;
    }
    printf("Enter some input: ");
    int c = EOF;
    unsigned int i = 0;
    while ((c = getchar()) != '\n' && c != EOF)
    {
        stringPtr[i++] = (char) c;
        if (i == strLength)
        {
            strLength *= strLength;
            if ((stringPtr = realloc(stringPtr, strLength)) == NULL)
            {
                fprintf(stderr, "Unable to expand memory to hold char array. Exiting!\n");
                return 2;
            }
        }
    }
    stringPtr[i] = '\0';
    if (sizeof(stringPtr) < strLength)
    {
        stringPtr = realloc(stringPtr, i);
    }
    printf("\n\nString value: %s\n\n\n", stringPtr);
    free(stringPtr);
    stringPtr = NULL;
}

Solution

  • I modified your program a bit to help figure out what's going wrong:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <errno.h>
    
    int main(void)
    {
        unsigned int strLength = 32;
        char *stringPtr = malloc(strLength);
        if (!stringPtr)
        {
            fprintf(stderr, "failed to allocate %u bytes: %s\n",
                    strLength, strerror(errno));
            return 1;
        }
        int c = EOF;
        unsigned int i = 0;
        while ((c = getchar()) != '\n' && c != EOF)
        {
            stringPtr[i++] = (char) c;
            if (i == strLength)
            {
                unsigned int nStrLength = strLength;
                nStrLength *= nStrLength;
                if (nStrLength <= strLength)
                {
                    fprintf(stderr, "cannot grow string of %u bytes any more\n",
                            strLength);
                    return 1;
                }
                if ((stringPtr = realloc(stringPtr, nStrLength)) == NULL)
                {
                    fprintf(stderr,
                            "failed to enlarge string from %u to %u bytes: %s\n",
                            strLength, nStrLength, strerror(errno));
                    return 1;
                }
                strLength = nStrLength;
            }
        }
        return 0;
    }
    

    When run more-or-less as you did, this is what I get:

    $ yes | tr -d '\n' | ./a.out 
    cannot grow string of 1048576 bytes any more
    

    1048576 is one megabyte, but more importantly, it's 220. The square of 220 is 240, which is bigger than 232 − 1, which is the largest value that can be represented in an unsigned int on this system. I predict that you will get the same results on your system.

    I therefore recommend you make three changes:

    • As I mentioned already, all of those unsigned int variables should be size_t instead.
    • As chux mentioned already, change your code to just multiply strLength by two instead of by itself.
    • Incorporate an explicit check for overflow along the lines of what I have done here. Or adopt reallocarray, which probably isn't in your C library but you can drop in from the link. [EDIT: reallocarray is still a good idea in general, but it doesn't help with this class of numeric-overflow bugs, because it's the number of items in the array that is overflowing, not the product of item count and size.]

    Also, it wasn't your immediate problem this time, but for future reference, strerror(errno) is your friend. Always print strerror(errno) when a system primitive fails.