-2

I am trying to read a string from the user using the following code

char array[4];
fgets(array , 5, stdin);

I am using the fgets command because with the scanf it reads the entire string regardless of the size of the array and if it doesn't fit inside the array it automatically changes the size of the array in order for the string to fit. I want always to read a string of maximum length 4, that is why I use the fgets, because fgets will always get the characters that you tell it to get regardless of how long the string from the user will be.

My problem is this, as you can see I have declared the array of size 4 but inside the fgets I have to write 5 because it reads one less character than the number. Why does it do that? Why does it read one character less than the number? am I doing something wrong?

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
programer
  • 1
  • 3
  • 2
    The array size must be equal to (or greater than) the size passed to `fgets`. And both sizes should be a big number (like 50 or 100 or more), comfortably greater than the largest string you can imagine the user typing. (Not "the length the user is supposed to type", but "the length you can imagine the user actually typing".) – Steve Summit Nov 16 '22 at 20:16
  • 3
    If the user types "abcd" and hits Return, you need an array of size *six*, because `fgets` is also going to store the newline `\n` and the string terminator `\0` in the array. – Steve Summit Nov 16 '22 at 20:18
  • 5
    *"it automatically changes the size of the array"*. The array does not change size but overflows. You have *undefined behaviour*. – Weather Vane Nov 16 '22 at 20:19
  • 1
    The size you pass to `fgets` is the size of your array. The reason you pass that size to `fgets` is so that it can be sure not to overflow the array. The reason is *not* so that `fgets` can somehow to force the user to type fewer characters than that. The user is going to type what the user is going to type. – Steve Summit Nov 16 '22 at 20:20
  • 2
    If you want to force the user to type a string of maximum length 4, you should (a) declare the array of size 100, (b) pass 100 to `fgets`, (c) [strip the newline](https://stackoverflow.com/questions/2693776), (d) call `strlen`, and finally (e) complain to the user if `strlen` returned a value greater than 4. – Steve Summit Nov 16 '22 at 20:22
  • [quick google research](https://cplusplus.com/reference/cstdio/fgets/) – emetsipe Nov 16 '22 at 20:34
  • If you use `char array[4];`, and if you correctly call `fgets(array, 4, stdin)`, you've got a situation where the user can type a string of at most *two* characters, plus the newline and the `\0`. If the user goes and types more than that, the good news is that the array won't overflow. But the bad news is that *the user's excess input will remain on `stdin`*, meaning it'll get read — wrongly — by the next part of your program that tries to read input. And you don't want that. – Steve Summit Nov 16 '22 at 20:35
  • @SteveSummit That is not correct. When I run that code, inside the array of size 4 it stores a string of size 4, no less, so I don't know about the '\0' and '\n' that you mentioned. Also why do you say that I have to set the array of size equal or greater than the size passed to fgets, I don't want a large string and it works without a large size. The code works perfectly, my question is why does fgets need a number greater than the size of the array by 1. – programer Nov 16 '22 at 21:07
  • @programer Please don't ask on a forum of experts and then assert that the answers you receive are "not correct". It's really quite rude. – Steve Summit Nov 16 '22 at 21:12
  • You've admitted you "don't know about the '\0' and '\n'", so you can't possibly understand what's actually going on here. Your code does not "work perfectly"; it is stomping on memory it does not own; it has *undefined behavior*; it is seeming to do what you want only by accident. It's impossible to answer the question "why does fgets need a number greater than the size of the array by 1?" when the premise of the question is false. It's simply not true that `fgets` needs a number greater than the size of the array: that's **wrong**. – Steve Summit Nov 16 '22 at 21:13
  • This is a frequently asked question, among many others that, as you ignore them, you're bound to spend plenty of time racking your brain, would be explained in a fraction of the time, **without you even asking**, if only you'd take the time to read K&R2e and do the exercises. To be clear, there are that many of these confusing hurdles in C, and the internet is at such heights of Dunning-Kruger, that **you cannot hope to learn how to write stable C code without help from a decent resource**. If this question arose from the contents of the textbook, it'd make sense to cite & ask about the words. – autistic Nov 16 '22 at 21:22
  • @SteveSummit I am not asserting the answer "incorrect" on a forum of "experts", I am telling you what I see, you say that an array of size 4 can store a string of maximum size 3 and I am telling you that I run the code above and inside the array of size 4 I successfully store a string of size 4, you can try it yourself if you don't believe me, so what you said is incorrect and when I said "I don't know about the '\0' and '\n'" I meant that I don't know what YOU mean, not that I don't know in general. I know what the '\0' and '\n' are. – programer Nov 16 '22 at 21:26
  • @programer Maybe you should read [this question](https://stackoverflow.com/questions/9137157) or [this question](https://stackoverflow.com/questions/6452959) or [this question](https://stackoverflow.com/questions/1239938). – Steve Summit Nov 16 '22 at 21:28
  • *I am telling you that I run the code above and inside the array of size 4 I successfully store a string of size 4* That is not correct. You cannot store a string of length 4 in an array of size 4 in C. You can *try*, and it might *seem* to work, but it's wrong. It's like driving through an intersection against a red light: you might get away with it, or you might not. – Steve Summit Nov 16 '22 at 21:30
  • @programer from SteveSummit's links you ought to see how common this kind of error that _creates instability_ is... It's so common, in fact, that he's written [this](https://c-faq.com/ansi/nonstrings.html) on the subject, which you'd know if only you'd actively **read all of the FAQs before continuing**... – autistic Nov 16 '22 at 21:30
  • Or [this question](https://stackoverflow.com/questions/11551472) or [this question](https://stackoverflow.com/questions/15646973) or [this question](https://stackoverflow.com/questions/55692816) or [this question](https://stackoverflow.com/questions/12410016). – Steve Summit Nov 16 '22 at 21:36
  • @ autistic I will read them, but @SteveSummit what do you mean that is incorrect, I have run the code above many times and inside the array that you see, of size 4 I store every time a string of size 4, the reason that this works is (I think) because C changes the size of the array in order for the array to fit in and I think that it also stores the '\0' inside automatically, so it changes the size so that the '\0' fits also – programer Nov 16 '22 at 21:36
  • Or see [this answer](https://stackoverflow.com/questions/57247807#57252388) or [this answer](https://stackoverflow.com/questions/57930992#57931054). – Steve Summit Nov 16 '22 at 21:37
  • C *never* changes the size of an array for you. Never. – Steve Summit Nov 16 '22 at 21:38
  • C is old, and a little strange in some places. If you're coming in and guessing like this, and making assumptions based on what you think you're seeing, you are *guaranteed* to learn some badly false lessons. – Steve Summit Nov 16 '22 at 21:39
  • @programer enough wasting time, you just asked [_another_ FAQ collated by Steve](https://c-faq.com/strangeprob/funnybugs.html), which you would discover sooner than later if only you'd do _less guessing and more reading_. – autistic Nov 16 '22 at 21:39
  • If you're not prepared to [read](https://c-faq.com/strangeprob/segv.html) then I don't know why you come here. We could repeat the same answers here, and duplicate the entire internet purely for your self... That'd be wasteful on our part. I'd rather let you know you're wasting your time, our time, the internet as a resource, etc, and that if you continue along these lines you're gonna struggle to meet your goals (of learning to write portable and stable C, for example, and possibly getting a job out of it). Or maybe you use this in production and get sued for damages ‍♂️ why should I care? – autistic Nov 16 '22 at 21:50

2 Answers2

1

A string in C is a sequence of characters including the terminating zero character '\0'.

So this array

char array[4];

can contain a string with in maximum three characters apart from the terminating zero character '\0'.

If you want to enter more than three characters you need to declare the array at least like

char array[5];

And then you can write

fgets(array , sizeof( array ), stdin);

In this case the new line character '\n' that corresponds to the pressed key Enter will not be stored in the array and will stay in the input buffer.

As a result if you after that will call fgets one more then a string that contains only that new line character '\n' (if not to count the terminating zero character '\0') will be read.

So it is better to declare the array like

char array[6];

To remove the new line character from the array you can write

array[ strcspn( array, "\n" ) ] = '\0';

As for this call

fgets(array , 5, stdin);

then it will invoke undefined behavior if you will try to enter four characters because the terminating zero character '\0' will be written by the function in memory outside the array.

As for the function scanf then you could use it the following way

char array[5];
scanf( "%4s", array );

or

char array[5];
scanf( " %4[^\n]", array );

Pay attention to the leading space in the format string. It allows to skip white space characters as for example the new ,line character '\n' that can be stored in the buffer after a preceding call of scanf.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • That is a very detailed answer. There is one thing I don't understand though. From what I understand you say that an array of size 4 can store a string of maximum size 3, but the code that I gave above when I run it, inside the array of size 4 it stores a string of size 4, not three. The problem is not in the size of the string that can be stored inside the array, but why do I have to write the number 5 instead of 4 inside the fgets. So I don't know what happens the the '\0' but I can store all four characters inside the array. is there something I don't understand correctly? – programer Nov 16 '22 at 21:00
  • @programer Your array contains 4 elements. But in the call of fgets you specified the size of the array equal to 5. So where will fgets write the terminating zero character '\0'? Obviously outside the array in memory that does not belong to the array. This invokes undefined behavior. Pay attention to that fgets also stores the terminating zero character '\0 in the destination array apart from the entered characters. – Vlad from Moscow Nov 16 '22 at 21:03
  • Ok, I understand what you mean. But it throws an error only when a character is stored outside the array, not when the '\0' is stored outside. If I write the number 6 (or greater) inside the fgets, then it will throw an error when the user writes a string of size greater than the size of the array, because it will store one or more charcters outside the array, but it doesn't throw an error when the size of the string given by the user is equal to the size of the array. According to what you say, it should throw an error even in that case, because it would store the '\0' outside the array. – programer Nov 16 '22 at 21:18
  • 1
    @programer Undefined behavior does not exactly mean that you will get a run-time error. It means that your program is incorrect. – Vlad from Moscow Nov 16 '22 at 21:20
  • I understand that and I agree but my question is why does it work in the one case and not in the other. Why does it throw an error when a character is stored outside the array and not when the NULL character is stored outside. I just want to understand why. – programer Nov 16 '22 at 21:30
  • @programer It is undefined behavior. How many times I should repeat that for you? – Vlad from Moscow Nov 16 '22 at 21:36
0

Writing fool-proof input routines is hard. Many traditional *nix programs used to fail when presented with unusual or extreme inputs (e.g. very long lines).

Unfortunately, beginner problems often deal with manual input. I see the following strategies, depending on the use case (e.g., the exact assignment).

  1. Ignore all input trouble and don't handle any errors. Assume that no word is longer than x bytes, no line longer than y bytes and no file larger than z bytes. This is not recommended, even if you are fairly sure that your manual input will not violate your arbitrary assumptions: Constraints are forgotten, routines get repurposed, and inevitably you'll have a program that malfunctions one way or another.
  2. Do some judicious error checking. This is what traditional *nix programs did before hacking became so much of an issue. Deal with the most common errors (file not found) and constraints that are the most likely to cause problems (line length), and fail when those are exceeded. Your program may still fail in certain situations, and it will be vulnerable to attacks.
  3. Make your program fool proof. This is relatively hard, even for simple programs, and tends to obscure the actual control flow and purpose with error checking and -handling. On the other hand you can be sure that when your routines is plugged into the software for the next bigger rocket it will trigger an assertion or otherwise fail gracefully.

In your case, you must:

  1. Make sure that the routine does not read more characters than space is available in the buffer, for which Vlad has given you the information.
  2. Think about how you detect words or lines that are too long.
  3. Handle the detected condition gracefully.

As an aside, scanf("%5s", buf) will also read up to 5 characters and no more, and store a null character after those so that your buffer array must be 6 characters big.

Peter - Reinstate Monica
  • 15,048
  • 4
  • 37
  • 62