cmemorymemory-managementscanfuser-input

Proper Memory Handling for User Input in C


I am trying to correctly handle user input in C, particularly when reading a file path from the user.

However, I have some concerns:

  1. How do I refactor this code to handle dynamic memory allocation.
  2. How should I properly handle dynamic memory allocation for user input when the length is unknown?
  3. What is the safest way to accept user input for file paths?

My current code:

#include <stdio.h>

int main() {
    char filePath[1024];

    printf("Please provide file to encrypt (File Path): ");
    scanf("%1024s", filePath); // Is this safe?

    printf("You entered: %s\n", filePath);
    return 0;
}

I found a source on Microsoft's website suggesting that I specify a width for the %s format specifier in scanf (e.g., %1024s instead of %s), but it's still fixed size and I want it dynamically allocated.


Solution

  • As noted in comments, you'd be better off utilizing the standard library fgets function which allows you to specify the size of the buffer to avoid buffer overflow issues.

    #include <stdio.h>
    
    int main() {
        char filePath[1024];
    
        printf("Please provide file to encrypt (File Path): ");
        fgets(filePath, sizeof(filePath), stdin);
    
        printf("You entered: %s\n", filePath);
        return 0;
    }
    

    Using scanf with %s with or without a width specifier will also restrict you to reading one whitespace delimited string. This prohibits spaces in your input.

    If you want to read an entire line of unknown length, you'll need to either go outside of the standard library or reinvent the wheel yourself. It shouldn't be difficult. I whipped the following up in a few minutes, and maybe there's room for improvement but that wouldn't take long either.

    The key process is to allocate an initial buffer, then read into it character by character until you hit EOF or a newline. If the length hits the limits of your buffer, reallocate.

    Two common memory management pitfalls with this:

    #include <stdlib.h>
    #include <stdio.h>
    
    char *read_line(FILE *fp) {
        size_t sz = 8;
        size_t len = 0;
        char *buf = malloc(sz);
        int ch;
    
        while ((ch = fgetc(fp)) != EOF) {
            // Grow the buffer if necessary by a factor of 2
            // to avoid extraneous calls to realloc
            if (len >= sz) {
                // Don't immediately overwrite the buf pointer 
                // in case realloc fails
                char *temp = realloc(buf, sz * 2);
                if (!temp) {
                    free(buf);
                    return NULL;
                }
    
                buf = temp;
                sz *= 2;
            }
    
            // Terminate on a newline
            if (ch == '\n') {
                buf[len] = '\0';
                return buf;
            }
    
            buf[len++] = ch;
        }
    
        buf[len] = '\0';
    
        return buf;
    }
    

    A further refinement of this might be to take a size_t pointer as an argument and allow the function to write the length of the read string to a variable, so that the size of the input string does not need to subsequently be calculated with strlen.


    1 Ideal memory growth rate is a matter of some debate. See: What is the ideal growth rate for a dynamically allocated array?