5

I am new to C, and I am trying to understand what goes on in fgets() when pointed to stdin.

Basically my question is this, forgive me I might not actually understand fgets() that well:

If I specify a pointer to some file to use in fgets(), then fgets() reads from that location onwards up to either \n, EOF, or the specified limit-1

So why does it behave differently if I point to stdin, in the sense, what makes it wait for user input rather than simply finding nothing to read and hence returning NULL?

Thank you for your time

Stefan
  • 55
  • 5
  • returning NULL means that end of file was reached. Not typing anything is different to end-of-file. – M.M Nov 27 '16 at 21:14
  • `fgets` always waits. The difference is that the wait times for a file on disk are so small that you don't notice them. – user3386109 Nov 27 '16 at 21:20
  • @user3386109 That's actually not correct. On a `FILE *` returned by `fmemopen` it might not wait, depending on the implementation of course. – Daniel Jour Nov 27 '16 at 21:26
  • @DanielJour Really??? – user3386109 Nov 27 '16 at 21:28
  • @user3386109 For example [musl libc](https://github.com/bminor/musl/blob/e738b8cbe64b6dd3ed9f47b6d4cd7eb2c422b38d/src/stdio/fmemopen.c#L106) bypasses the operating system for such files, thus there it is just a function call via function pointer. – Daniel Jour Nov 27 '16 at 22:00

2 Answers2

4

fgets() reads from the argument stream. If this stream is tied to a device or a pipe, it blocks until input is obtained from the device/pipe or until an end of file is detected.

stdin is usually tied to the terminal. Reading from the terminal reads any pending input and in the case of fgets() keeps reading until a newline is typed or enough characters are input. There is one extra layer to understand here: the kernel driver for the terminal performs its own buffering by default, causing the input operation to block until a newline is typed even if more characters are typed than fgets() expects. These extra characters are left pending in the terminal buffer.

The terminal can be configured for raw mode (as opposed to the default cooked mode) with the stty system call (on Posix systems). Doing this would remove the buffering in the device driver, but buffering would still be performed in the FILE * stream. fgets() can only access characters from the stream after this buffering is performed. Streams tied to devices are usually line buffered by default, causing the stream buffering to match the device drive buffering. If you set both the device to raw mode and the stream as unbuffered, characters will be available to fgets() as they are typed and fgets() will stop reading when it gets a newline or when it has read size-1 characters.

Note also that you can enter an end of file in cooked mode by typing Control-D on unix and OS/X systems and Control-Z Enter on Windows systems.

chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • If I understood correctly, I am dealing with a terminal buffer which, if not previously filled, will wait for a newline before it is sent for fgets() to read, which will be halted during the whole time. Thanks for the extra information, it clears things up! – Stefan Nov 27 '16 at 21:39
  • Both the terminal and stdin perform some form of line buffering by default. if for example you read an integer with `scanf("%d", &n);`, the rest of the line read by the terminal and sent to the stream is still ending in the `stdin` stream buffer. `fgets()` will read that and will not request any input from the terminal since it will have seen the newline that was pending. – chqrlie Nov 27 '16 at 21:49
3

If I specify a pointer to some file to use in fgets(), then fgets() reads from that location onwards up to either \n, EOF, or the specified limit-1

It doesn't read from that location, it reads from that file.

So why does it behave differently if I point to stdin, in the sense, what makes it wait for user input rather than simply finding nothing to read and hence returning NULL?

It doesn't behave any differently. The fgets function only accepts a pointer to a file and it always reads from that file.

I suspect you're confusing fgets with some other function that does something else. The fgets function is, and only is, a function to do a blocking read from a file.

David Schwartz
  • 179,497
  • 17
  • 214
  • 278
  • What makes `fgets` wait however? If I were to specify `fgets` to read from an empty file it would not wait for the file to be filled with anything. Thanks for the answer! – Stefan Nov 27 '16 at 21:24
  • @Stefan That has to do with the defined semantics of blocking reads on different types of devices. The `fgets` function gives you a blocking read, what that means depends on the device. Files are typically defined as "fast" devices that wait only for hardware operations to complete. Terminals, pipes and sockets are "slow" devices that can wait for things to happen. – David Schwartz Nov 27 '16 at 21:27
  • Oh I see! So basically fgets() gets halted if it tries to read from the terminal and there is nothing to read by default? – Stefan Nov 27 '16 at 21:34
  • @Stefan Yes, that's how "slow" devices like terminals, pipes, and sockets behave when you perform a blocking read on them. "Fast" devices like files do not behave that way. The `fgets` function gets you a blocking read, what that means depends on the device. – David Schwartz Nov 27 '16 at 23:13