-2

Safe input is harder than I thought. I looked around and found this, but it is from 2011 and things might have changed since then.

I'm looking for something that can handle string input and single character input. For example:

Hello, what's your name?
My name is: _
Are you sure?
[Y] Yes, hello
[N] No, goodbye
_

Here is the way I'm doing it right now:

char input[16];
fgets(input, 16, stdin);
char input = getchar();

My problem has always been that the user may input arbitrarily long input or invalid input. How can I read input safely and ensure future inputs won't get borked?

Looking for solutions that work in C and across Linux / Windows.

Thank you for your time.

  • 1
    *... Afterwards, flush stdin so ...* – Don't ever think if you can `fflush(stdin)`. It would invoke UB. – Rohan Bari Feb 23 '21 at 13:44
  • In C its very easy to make a dynamic string. in C++ you can use `std:string foo; std::cin >> foo`. If you want, I can write the C version as an answer? – Import Accelerate Feb 23 '21 at 13:46
  • 1
    @ImportAccelerate Wait with that until OP has made up their mind about what language this question is about. – klutt Feb 23 '21 at 13:47
  • You might be interested in reading: [Reading from stdin as characters are typed](https://stackoverflow.com/questions/10004895/c-reading-from-stdin-as-characters-are-typed) and [Capture characters from standard input](https://stackoverflow.com/questions/421860/capture-characters-from-standard-input-without-waiting-for-enter-to-be-pressed) – rustyx Feb 23 '21 at 13:49
  • You can also use `termios.h` to go into raw mode but that's Unix-specific – Import Accelerate Feb 23 '21 at 13:50
  • @klutt How about I just provide both? – Import Accelerate Feb 23 '21 at 13:51
  • @klutt Apologies, I narrowed the language I'm looking for. – 404 Name Not Found Feb 23 '21 at 13:51
  • @404NameNotFound So should I post the way to read infinite input safely in C? – Import Accelerate Feb 23 '21 at 13:51
  • @ImportAccelerate IMO, it's then better if you write a self answered question for C++, now when OP has narrowed it down to C – klutt Feb 23 '21 at 13:52
  • 1
    @RohanBari Yes I know I can't flush `stdin` that way, my intention is so that future inputs don't get borked. A common experience for me is forgetting that a newline character still exists in the input buffer, borking up next reads. – 404 Name Not Found Feb 23 '21 at 13:52
  • You might want to have a look here OP https://stackoverflow.com/q/58403537/6699433 – klutt Feb 23 '21 at 13:52
  • Yeah I have a solution for C using malloc, realloc and getchar – Import Accelerate Feb 23 '21 at 13:52
  • @404NameNotFound I wrote an answer to the question I linked. The main thing is "Read data with fgets and process it with sscanf. Never read with scanf." – klutt Feb 23 '21 at 13:54
  • You should never write `char input = getchar()`, because `getchar` returns an int. You must store it in an `int` so that you can check against EOF. – William Pursell Feb 23 '21 at 13:54
  • @ImportAccelerate Could you please also do a C++ one? I'm also intersted in that. – 404 Name Not Found Feb 23 '21 at 14:03
  • C++ version is just `std::string input; std::cin >> input;` full stop. Also, if you think my answer is correct, you can press the Checkmark button next to it. – Import Accelerate Feb 23 '21 at 17:19

1 Answers1

0

You are able to read infinite standard input safely. You can just use a continuously reallocating buffer.

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

char* get_line(size_t* length) {
    size_t len = 0;
    size_t cap = 4;
    char* buffer = malloc(cap);
    if (buffer == NULL) {
        fprintf(stderr, "Virtual memory exhausted\n");
        return NULL;
    }

    char current;
    while ((current = getchar()) != '\n') {
        if (len + 1 > cap) {
            cap *= 2;
            buffer = realloc(buffer, cap);
        }
        buffer[len++] = current;
    }
    // One last time for NUL terminator
    if (len + 1 > cap) {
        cap *= 2;
        buffer = realloc(buffer, cap);
    }        
    buffer[len] = 0;

    if (length) *length = len;
    return buffer;
}

This function will read stdin char-by-char and add it to a string which is returned. Optionally you can ask for its length. Don't forget to free.

int main() {
    char* line = get_line(NULL);
    printf("You entered: %s\n", line);
    free(line);
}

NOTE: This code is meant to be of little verbosity so some checks were omitted. In production code checking realloc and using int for getchar() to test for EOF is necessary.

  • You did not check return value of `realloc` – klutt Feb 23 '21 at 13:57
  • That is true. I will add a note that this is meant to be of little verbosity and that in production code checking `realloc` is necessary. – Import Accelerate Feb 23 '21 at 13:57
  • If I may ask, what is the parameter `size_t* length` for here? Is it for "Optionally you can ask for its length"? Also, does reading character by character not incur overhead? I don't have any statistics to verify, but I feel like reading by character would be less efficient than directly reading a chunk. Or is it negligible? – 404 Name Not Found Feb 23 '21 at 18:57
  • There's not much overhead here since most terminals are line-buffered so all the characters are already available. `size_t* length` is an optional parameter, pass a pointer to a `size_t` and it will store the string length there to avoid a subsequent `strlen`. Otherwise just pass `NULL`. – Import Accelerate Feb 24 '21 at 16:49