0

The following code works, but if I input more than 10 characters (let's say 10 a's), the output turns into something like this:

"Dog's name? aaaaaaaaaDog's breed?Dog's name: aaaaaaaaaDog's breed:"

Why is this? And how can I fix it?

#include <stdio.h>
#include <stdlib.h>

typedef struct Dog {
    char name[10];
    char breed[10];
} Dog;

Dog makeDog() {
    Dog dog;

    printf("Dog's name? ");
    fgets(dog.name, 10, stdin);

    printf("Dog's breed? ");
    fgets(dog.breed, 10, stdin);

    return dog;
}

int main() {
    printf("\n");

    Dog dog = makeDog();

    printf("\n");

    printf("Dog's name: %s", dog.name);
    printf("Dog's breed: %s \n", dog.breed);
}
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
Yogsoth
  • 3
  • 1
  • Related : https://stackoverflow.com/questions/1660228/does-fgets-always-terminate-the-char-buffer-with-0 – MartinVeronneau Jun 18 '19 at 18:03
  • 1
    There is a reason why `fgets()` retains the final newline. If it is not present in the input, the input was truncated and the rest of it is still in the input buffer (unless it's the last line of a file and lacks a newline). BTW in the usual case, you will need to [remove that newline](https://stackoverflow.com/questions/2693776/removing-trailing-newline-character-from-fgets-input). To fix it, use a bigger array, or dynamically allocate memory, or exit the program because it can't hold the data you entered. – Weather Vane Jun 18 '19 at 18:07
  • 1
    What do you mean by "fix it"? It appears to be working correctly. – William Pursell Jun 18 '19 at 18:34
  • 1
    No arrays are being overwritten. You gave input of 10 'a's and a newline. 9 of those 'a's were written into dog.name, one of the 'a's and the newline were put into dog.breed. – William Pursell Jun 18 '19 at 18:37
  • @WilliamPursell The problem is that it skips the next fgets statement if I input more than ten a's. – Yogsoth Jun 18 '19 at 18:44
  • with `fgets()` only a max of (in this case) 9 characters will be input and the 10th position in the array will be set to `'\0'` So there are still characters 'a' in `stdin` – user3629249 Jun 18 '19 at 18:52
  • in function: `makeDog()` regarding: `Dog dog;` and `return dog;` Since the strut `Dog is on the stack, and will 'go out of scope' when the function exits. Accessing it in the calling function is undefined behavior – user3629249 Jun 18 '19 at 18:57
  • 1
    It doesn't skip the statement. The second call to fgets reads the rest of the line. If you want to discard that data, you need to write some code to do so. – William Pursell Jun 18 '19 at 18:58
  • @user3629249 Thank you. How would I go about discarding the characters still left in stdin before calling fgets again? – Yogsoth Jun 18 '19 at 19:03
  • @user3629249 No. If a pointer to the variable is returned, that would be a problem. But this is no different than a function returning int doing `int f; f = 5; return f;`. – William Pursell Jun 18 '19 at 19:40
  • regarding: *How would I go about discarding the characters still left in stdin before calling fgets again?* Please read/understand my answer. Then IF the read dog->name does not contains a '\n' (newline) then use: `int ch; while( (ch = getchar()) != EOF && ch != '\n' ){;}` – user3629249 Jun 18 '19 at 19:41
  • @WilliamPursell, are you referring to the 'undefined behavior'? an individual/native field will be returned in 1 or 2 CPU registers. However, for larger items to return, like the 20 byte `struct dog`, problems will arise. That is why I suggest, in my answer, to use dynamic memory allocation and return a pointer to that dynamic memory – user3629249 Jun 18 '19 at 19:44
  • to understand what is wrong with the OPs code, you need to read/understand all the details abut the `fgets()` function. All the C library functions are completely described in the MAN pages. If you don't have the MAN pages on your computer, then search "syntax of in c" – user3629249 Jun 20 '19 at 04:44

2 Answers2

0

the following proposed code:

  1. cleanly compiles
  2. performs the desired functionality
  3. exits if dog name too long
  4. removes newline at end of dog name
  5. removes newline at end of dog breed
  6. does not check validity of dog breed length
  7. properly cleans up after using dynamic memory
  8. avoids using 'magic' numbers
  9. properly allocates dynamic memory and checks/handles any error
  10. uses: 'max 8 characters' to allow room for trailing newline and terminating NUL character

and now, the proposed code:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_NAME_LEN  10
#define MAX_BREED_LEN 10

typedef struct Dog 
{
    char name[ MAX_NAME_LEN ];
    char breed[ MAX_BREED_LEN ];
} Dog;

Dog * makeDog() 
{
    Dog *dog = malloc( sizeof( Dog ) );
    if( !dog )
    {
        perror( "malloc for struct Dog failed" );
        exit( EXIT_FAILURE );
    }

    printf("Dog's name? max 8 characters ");
    if( !fgets(dog->name, MAX_NAME_LEN, stdin) )
    {
        perror( "fgets for dog name failed" );
        exit( EXIT_FAILURE );
    }

    if( dog->name[ strlen( dog->name ) -1 ] != '\n' )
    {
        puts( "dog name too long, aborting" );
        exit( EXIT_FAILURE );
    }

    // remove trailing newline
    dog->name[ strcspn( dog->name, "\n" ) ] = '\0';

    printf("Dog's breed? max 8 characters");
    if( !fgets(dog->breed, MAX_BREED_LEN, stdin) )
    {
        perror( "fgets for dog breed failed" );
        exit( EXIT_FAILURE );
    }

    // remove trailing newline
    dog->breed[ strcspn( dog->name, "\n" ) ] = '\0';

    return dog;
}

int main() {
    printf("\n");

    Dog *dog = makeDog();

    printf("\n");

    printf("Dog's name: %s\n", dog->name);
    printf("Dog's breed: %s\n", dog->breed);

    free( dog );
}
user3629249
  • 16,402
  • 1
  • 16
  • 17
  • Thank you for the effort even though this is a bit too complex for me at my current level of C. I will do my best to try to understand it. – Yogsoth Jun 18 '19 at 20:05
0

The character array name is declared with 10 elements

char name[10];

If you are using the following call of fgets

fgets(dog.name, 10, stdin);

after entering 10 characters 'a' then the fgets call reads only 9 characters from the input buffer and appends the array with the terminating zero character '\0'.

So the array will contain the string "aaaaaaaaa". It is the same as to initialize the array the following way

char name[10[ = { 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', '\0' };

After that the input buffer will contain one character 'a' and the new line character '\n'. These characters will be read by the next call

fgets(dog.breed, 10, stdin);

As result the array bread will contain the string "a\n".

It is the same as to initialize the array the following way

char bread[10[ = { 'a', '\n', '\0' };

If you want to store in the arrays strings with more characters you should enlarge the arrays.

For example if you want to enter for the array name a string of 10 characters 'a' you have tfo declare the array as having 12 elements. Why 12? Because apart from 10 characters 'a' and the terminating zero character the function fgets also will try to read the new line character '\n' from the input buffer. Otherwise this character will be read by a second call of fgets.

To remove the new line character from an array you can use the following approach

#include <string.h>

//...

fgets( dog.name, 12, stdin );
dog.name[strcspn( dog.name, "\n" )] = '\0';
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335