简体   繁体   中英

How does fgets work in this program and how does it tie into the 'stream' concept?

I am having difficulty with a feature of a segment of code that is designed to illustrate the fgets() function for input. Before I proceed, I would like to make sure that my understanding of I/O and streams is correct and that I'm not completely off base:

Input and Output in C has no specific viable function for working with strings. The one function specific for working with strings is the 'gets()' function, which will accept input beyond the limits of the char array to store the input (thus making it effectively illegal for all but backward compatibility), and create buffer overflows.

This brings up the topic of streams, which to the best of my understanding is a model to explain I/O in a program. A stream is considered 'flowing water' on which the data utilized by programs is conveyed. See links: (also as a conveyor belt)

Can you explain the concept of streams? What is a stream?

In the C language, there are 3 predefined ANSII streams for standard input and output, and 2 additional streams if using windows or DOS which are as follows:

  • stdin (keyboard)
  • stdout (screen)
  • stderr (screen)
  • stdprn (printer)
  • stdaux (serial port)

As I understand, to make things manageable it is okay to think of these as rivers that exist in your operating system, and a program uses I/O functions to put data in them, take data out of them, or change the direction of where the streams are flowing (such as reading or writing a file would require). Never think of the 'beginning' or 'end' of the streams: this is handled by the operating system. What you need to be concerned with is where the water takes your data, and that is mediated by use of specific functions (such as printf() , puts() , gets() , fgets() , etc.).

This is where my questions start to take form. Now I am interested in getting a grasp on the fgets() function and how it ties into streams. fgets() uses the 'stdin' stream (naturally) and has the built in fail safe (see below) that will not allow user input to exceed the array used to store the input. Here is the outline of the fgets() function, rather its prototype (which I don't see why one would ever need to declare it?):

char *fgets(char *str , int n , FILE *fp);

Note the three parameters that the fgets function takes:

p1 is the address of where the input is stored (a pointer, which will likely just be the name of the array you use, eg, 'buffer')

p2 is the maximum length of characters to be input (I think this is where my question is!)

p3 specifies the input stream, which in this code is 'stdin' (when would it ever be different?)

Now, the code I have below will allow you to type characters until your heart is content. When you hit return, the input is printed on the screen in rows of the length of the second parameter minus 1 (MAXLEN -1). When you enter a return with no other text, the program terminates.

#include <stdio.h>

#define MAXLEN 10

int main(void)
{
char buffer[MAXLEN];

puts("Enter text a line at a time: enter a blank line to exit");

while(1)
{
        fgets(buffer, MAXLEN, stdin);    //Read comments below. Note 'buffer' is indeed a pointer: just to array's first element.

        if(buffer[0] == '\n')
        {
            break;

        }
        puts(buffer);           
}
return 0;
}

Now, here are my questions:

1) Does this program allow me to input UNLIMITED characters? I fail to see the mechanism that makes fgets() safer than gets() , because my array that I am storing input in is of a limited size (256 in this case). The only thing that I see happening is my long strings of input being parsed into MAXLEN - 1 slices? What am I not seeing with fgets() that stops buffer overflow that gets() does not? I do not see in the parameters of fgets() where that fail-safe exists.

2) Why does the program print out input in rows of MAXLEN-1 instead of MAXLEN?

3) What is the significance of the second parameter of the fgets() function? When I run the program, I am able to type as many characters as I want. What is MAXLEN doing to guard against buffer overflow? From what I can guess, when the user inputs a big long string, once the user hits return, the MAXLEN chops up the string in to MAXLEN sized bites/bytes (both actually work here lol) and sends them to the array. I'm sure I'm missing something important here.

That was a mouthful, but my lack of grasp on this very important subject is making my code weak.

Question 1

You can actually type as much character as your command line tool will allow you per input. However, you call to fgets() will handle only MAXLEN in your example because you tell him to do so.

Moreover, there is no safe check inside fgets(). The second parameter you gave to fgets is the "safety" argument. Try to give to change your call to fgets to fgets(buffer, MAXLEN + 10, stdin); and then type more than MAXLEN characters. Your program will crash because you are accessing unallocated memory.

Question 2

When you make a call to fgets(), it will read MAXLEN - 1 characters because the last one is reserved to the character code \\0 which usually means end of string

The second parameter of fgets() is not the number of character you want to store but the maximum capacity of your buffer. And you always have to think about string termination character \\0

Question 3

If you undestood the 2 answer before, you will be able to answer to this one by yourself. Try to play with this value. And use a different value than the one used for you buffer size.

Also, you said

p3 specifies the input stream, which in this code is 'stdin' (when would it ever be different?)

You can use fgets to read files stored on your computer. Here is an example :

char buffer[20];
FILE *stream = fopen("myfile.txt", "r"); //Open the file "myfile.txt" in readonly mode

fgets(buffer, 20, stream); //Read the 19 first characters of the file "myfile.txt"
puts(buffer);

When you call fgets(), it lets you type in as much as you want into stdin , so everything stays in stdin. It seems fgets() takes the first 9 characters, attaches a null character, and assigns it to buffer . Then puts() displays buffer then creates a newline.

The key is it's in a while loop -- the code loops again then takes what was remaining in stdin and feeds it into fgets(), which takes the next 9 characters and repeats. Stdin just still had stuff "in queue".

Input and Output in C has no specific viable function for working with strings.

There are several functions for outputting strings, such as printf and puts .

Strings can be input with fgets or scanf ; however there is no standard function that both inputs and allocates memory. You need to pre-allocate some memory, and then read some characters into that memory.


Your analogy of a stream as a river is not great. Rivers flow whether or not you are taking items out of them, but streams don't. A better analogy might be a line of people at the gates to a stadium.

C also has the concept of a "line", lines are marked by having a '\\n' character at the end. In my analogy let's say the newline character is represented by a short person.

When you do fgets(buf, 20, stdin) it is like "Let the next 19 people in, but if you encounter a short person during this, let him through but not anybody else". Then the fgets function creates a string out of these 0 to 19 characters, by putting the end-of-string marker on the end; and that string is placed in buf .

Note that the second argument to fgets is the buffer size , not the number of characters to read.

When you type in characters, that is like more people joining the queue.

If there were fewer than 19 people and no short people, then fgets waits for more people to arrive. In standard C there's no way to check if people are waiting without blocking to wait for them if they aren't.

By default, C streams are line buffered . In my analogy, this is like there is a "pre-checking" gate earlier on than the main gate, where all people that arrive go into a holding pen until a short person arrives; and then everyone from the holding pen plus that short person get sent onto the main gate. This can be turned off using setvbuf .

Never think of the 'beginning' or 'end' of the streams: this is handled by the operating system.

This is something you do have to worry about. stdin etc. are already begun before you enter main() , but other streams (eg if you want to read from a file on your hard drive), you have to begin them.

Streams may end. When a stream is ended, fgets will return NULL . Your program must handle this. In my analogy, the gate is closed.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM