0

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, e.g., '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.

Community
  • 1
  • 1
  • I would like to cite my code from Sam's "Teach Yourself C in 21 Days", copyright 2003 by Jones and Aitken – Smith Will Suffice Oct 26 '13 at 17:45
  • fgets won't put more characters than MAXLEN - 1 inside your buffer, that's what make it _safe_. Of course it won't warn user in any way (because it works with streams so it can't assert input comes from console) then it can't print something, beep or anything else. It'll just ignore them. Of course YOU can warn user (for example if he typed MAXLEN - 1 characters). Check its return value to see it. – Adriano Repetti Oct 26 '13 at 17:46
  • That said...1) yes, keeping limit of MAXLEN - 1 characters per line you can enter as much text as you want (because _old_ text not stored anywhere). 2) MAXLEN - 1 because strings in C must be **terminated with NULL character**. 3) you're right you can type as many as you want but it won't store them. You gave it the size of the buffer. Imagine you fill your notebook with text, when you finish your blank sheets you just stop to write... – Adriano Repetti Oct 26 '13 at 17:47
  • About return value: just check if it includes new line. If not then user typed more characters than MAXLEN and it has been truncated. – Adriano Repetti Oct 26 '13 at 18:01
  • It isn't obvious, but if you type *exactly* MAXLEN-1 characters, then thump the enter key on your keyboard, this will print your entered data *and* terminate the loop. The "When you enter a return with no other text, the program terminates" assumption as the only way this terminates isn't accurate. – WhozCraig Oct 26 '13 at 18:12
  • @WhozCraig you're right, buffer should be allocated for MAXLEN + 2 characters. – Adriano Repetti Oct 26 '13 at 18:24
  • @Adriano its not the buffer size; its the logic in the loop. The code assumes the only way you get a newline in slot-0 is by entering a newline on a blank line. That isn't the case. `fgets()` will consume data until MAXLEN-1 **or** a newline is discovered, whichever comes *first*. If you reach MAXLEN-1 as the character just *prior* to your newline, the size-limit condition is tripped and `fgets` terminates and returns just the data up to, but not including, the newline. The next go-around the loop will continue where it left off, and this time get the newline as the *first* char. – WhozCraig Oct 26 '13 at 18:28
  • @WhozCraig I mean: given MAXLEN as maximum input length then it _must_ constains a new line (if buffer is allocated for MAXLEN + 2 characters). If it doesn't than it's empty or user typed more characters than MAXLEN. That said yes, you will consume _extra_ characters with more calls (how to handle that depends on requirements, may truncate and ignore too long lines, may process them as separate lines or put them all together)... – Adriano Repetti Oct 26 '13 at 19:27
  • @Adriano the point is there is no maximum input length restriction (beyond the implementation restriction on stdin). There is only a maximum of what we can *process*. No matter what the buffer size is, if `fgets()` by our restricted buffer size happens to fall short immediately before the newline, it will NOT consume it and it will be there waiting for the next loop iteration. The only way to ensure that doesn't happen is to ensure your last `fgets()` *did* get a newline (easily checked as the last char) for any input where the newline is *not* in the 0-slot. – WhozCraig Oct 26 '13 at 19:37
  • @WhozCraig true, I didn't get the point of that! – Adriano Repetti Oct 26 '13 at 19:47
  • "and 2 additional streams if using windows or DOS which are as follows" => stdprn and stdaux do not exist in Windows, except for DOS apps and 16-bit Windows apps (from Windows 3.x and earlier). If you are writing a Windows app in this century (unless you are some kind of retrocomputing enthusiast), your app is 32-bit or 64-bit and those streams aren't available – Simon Kissane Nov 07 '22 at 09:14

3 Answers3

2

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);
Masadow
  • 762
  • 5
  • 15
1

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".

Sulthan Allaudeen
  • 11,330
  • 12
  • 48
  • 63
Dave
  • 11
  • 1
0

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 (e.g. 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.

M.M
  • 138,810
  • 21
  • 208
  • 365