This is a broad question that touches on two important points, the algorithm, and the specifics of the C implementation, so I will briefly address both.
The Algorithm
As a human doing this by hand, one could go about it this way:
- Keep a counter of the inputs, initially zero
- Increment the counter
- Print the counter
- Ask for input
- Back to step 2
This would repeat indefinitely until the user aborts. Or we could choose an arbitrary number of inputs, say five. Then before going back to step 2, the program would check if the counter reached the limit.
Since Python reads nicely like pseudocode we can use it for our draft algorithm:
#!/usr/bin/env python3
input_count = 0
max_count = 5
while input_count < max_count:
input_count += 1
line = input("%d: " % input_count)
# do something with line, e.g.:
print(line)
I've used the printf style here, but I usually prefer the newer formatted string literals. But enough about Python.
The C Implementation
Implementing this simple program in C can be slightly more involved because C is a relatively low level language. Unlike the Python version, C requires the variable that will hold the string to be given a specific size, unless we wish to do dynamic memory allocation, which we won’t do here, but more on that later. A simple version could look like this:
#include <stdio.h>
#define MAX_LINE 120
void main(void)
{
char line[MAX_LINE];
int input_count = 0;
const int max_count = 5;
while (input_count++ < max_count) {
printf("%d: ", input_count);
if (fgets(line, sizeof(line), stdin) != NULL) {
/* do something with line, e.g.:
printf("%s", line);
*/
}
}
}
Infinite Lines
This is nice, but what if we don’t know how many lines users want to enter? We could stop the program when they press Enter on a blank line, instead of a fixed number of times, like 5. A slight modification to the above lets us do just that:
#include <stdio.h>
#include <stdbool.h>
#define MAX_LINE 120
void main(void)
{
char line[MAX_LINE];
int input_count = 1;
bool done = false;
do {
printf("%d: ", input_count++);
if (fgets(line, sizeof(line), stdin) != NULL) {
/* do something with line, e.g.:
printf("%s", line);
*/
}
if (line[0] == '\n') {
done = true;
}
} while (!done);
}
The Overflow Issue
So far we assumed that our users will behave and always type less characters than the size allowed by the buffer, including the newline character when they press Enter, plus the null terminator byte. But a good program also takes care of errors and alternative scenarios.
To see the overflow behavior more easily, let’s reduce our buffer to 12, so that it allows only 10 visible characters if the string includes the newline character besides the terminator. It also prints the value in the line buffer, so we can see the result:
#include <stdio.h>
#include <stdbool.h>
#define MAX_LINE 12
void main(void)
{
char line[MAX_LINE];
int input_count = 1;
bool done = false;
do {
printf("%d: ", input_count++);
if (fgets(line, sizeof(line), stdin) != NULL) {
printf("%s\n", line);
}
if (line[0] == '\n') {
done = true;
}
} while (!done);
}
Here is a sample session:
1: 123456789ABCDEF
123456789AB
2: CDEF
3:
All I did here was enter 16 characters: 123456789ABCDEF, followed by Enter. But we can see above that a second input was counted, which has CDEF. Then I pressed Enter at prompt 3, which ended the program.
What happened here? Since the size of the buffer was exceeded, fgets() stopped reading characters and stored just enough characters to fill the buffer, including the null terminator. So that’s 11 visible characters, or 123456789AB. Then at the next loop iteration it prints the input count of 2. It then proceeds to call fgets() again. However, because there are unread characters in the standard input stream, fgets() will read those first until it encounters the newline character, since they perfectly fit in the buffer now. That line is printed before I have chance to type anything, and the loop moves on to input number 3, since the newline indicates the end of input 2.
This is undesirable. The truncation is expected, it’s a feature of fgets() that makes it safe to use, unlike scanf() or gets(), which allow dangerous buffer overflows at run-time. But the downside is that the second input is now "corrupted" with the stdin overflow of the first one.
Some may be tempted to use fflush to discard the unwanted input, but that is not meant for the terminal and has undefined or no effect.
Since fgets() is not polite enough to return a value to tell the caller that an overflow occurred, we’ll use an alternative solution for input, with getchar(), which will give us greater control.
We will start with an initial version, using a function called input() which can be used as a drop-in replacement for fgets(), except it doesn’t required the third parameter, since we’ll always use stdin:
#include <stdio.h>
#include <stdbool.h>
#define MAX_LINE 12
char *input(char *line, size_t buffer_len)
{
size_t len = 0;
bool done = false;
int keychar;
while (!done && len < buffer_len && (keychar = getchar()) != EOF) {
line[len++] = keychar;
if (keychar == '\n' || len == buffer_len - 1) {
done = true;
}
}
if (len < buffer_len) {
line[len] = '\0';
return line;
}
/* Error, buffer too small */
return NULL;
}
void main(void)
{
char line[MAX_LINE] = "";
int input_count = 1;
bool done = false;
do {
printf("%d: ", input_count++);
if (input(line, sizeof(line)) != NULL) {
printf("%s\n", line);
} else {
printf("Error reading input\n");
break;
}
} while (line[0] != '\n');
}
Running this program will show the same overflow problem noted earlier with fgets(), but now that we’re in control, we can change it to this final version:
#include <stdio.h>
#include <stdbool.h>
#define MAX_LINE 12
bool input(char *line, size_t buffer_len)
{
size_t len = 0;
bool done = false;
int keychar;
bool truncated = false;
bool buffer_error = true;
while (!done && len < buffer_len && (keychar = getchar()) != EOF) {
line[len++] = keychar;
if (keychar == '\n' || len == buffer_len - 1) {
done = true;
}
}
if (keychar != '\n') {
truncated = true;
/* consume truncated chars */
while ((keychar = getchar()) != '\n') {
if (keychar == EOF)
break;
}
}
if (len < buffer_len) {
line[len - 1] = '\n'; /* for consistency */
line[len] = '\0';
buffer_error = false;
}
return !truncated && !buffer_error;
}
void main(void)
{
char line[MAX_LINE] = "";
int input_count = 1;
bool done = false;
do {
printf("%d: ", input_count++);
if (input(line, sizeof(line))) {
printf("%s\n", line);
} else {
/* Ignoring the buffer error scenario */
printf("Input truncated: %s\n", line);
}
} while (line[0] != '\n');
printf("Goodbye!\n");
}
Let’s try it now with overflow:
1: 123456789ABCDEF
Input truncated: 123456789A
2: 123456789A
123456789A
3: It works!
It works!
4:
Goodbye!
As we can see, truncation is now detected, and it does not interfere with the subsequent input. Of course, we could still use fgets() and always take care to consume unwanted input before each call. I just wanted to demonstrate the usage of both fgets() and getchar() which are two of the most important standard functions for string console input.
Alternative String Input Solutions
There are some other solutions for keyboard string input, which are non-standard and therefore less portable. Some people might have encountered getch and getche, which were introduced by Microsoft or Borland in the days of MS-DOS, in the conio.h header. I believe it was also supported by Turbo C/C++, a popular IDE and compiler by Borland back then. There's a question on how to get it on Linux.
Microsoft still provides _getch() for input without echoing, which could let you do cool things, and the companion _getche() function which does echo the character, much like getchar(). They also have a safe version of scanf, which allows specifying the buffer size, like we do with fgets, called scanf_s.
To avoid dealing with truncation altogether, GNU has added getline() which is now standardized in POSIX.1-2008. There’s also an extension to scanf, which includes an optional 'm' character, which dynamically allocates a buffer large enough to hold the input string. Finally, there’s readline, a fancy, some might say heavy, GNU extension that gives a prompt with editing capability. In a Linux environment, those are all valid options.
Conclusion
If this “brief” answer seems too long, it’s because there’s much to say about input and output in C. I’ve barely touched the surface here. There are plenty of good books out there which cover things in more detail.
To those getting started with C, I’d recommend sticking with fgets() and getchar() when playing around. It’s better than becoming accustomed to reading strings with unsafe functions like scanf or gets. When the time comes to do serious coding, you’ll then have the choice of using non-standard and platform specific solutions or implementing your own.