[SOLVED] Gets() behaving like fgets()?

That means your computer is super weird 'cause that code works on my machines. I mean, unless something changed in C since then so maybe I should go try it. Can you do this for me:

  1. Take my code.
  2. Run it so you cause the error you see, but write down every tiny step you make.
  3. Write those up in your reply so I can replicate it with my file.
  4. Alternatively, do a video demonstrating.
  5. Tell me your exact OS and compiler versions.

I’ll go try it out.

  1. Set MAX_DATA at the top to 10
  2. Run make ex24
  3. Run ./ex24
  4. Type in dddddddddddddddddddddddddddddddddddddddddddddddddddddddd

OS:
Ubuntu 18.04.1 x86_64

Output of gcc -v:

Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/7/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 7.3.0-27ubuntu1~18.04' --with-bugurl=file:///usr/share/doc/gcc-7/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++ --prefix=/usr --with-gcc-major-version-only --program-suffix=-7 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-libmpx --enable-plugin --enable-default-pie --with-system-zlib --with-target-system-zlib --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 7.3.0 (Ubuntu 7.3.0-27ubuntu1~18.04) 

EDIT: Updated ex22 to ex24

Heres what I get:

bruda@cyborg ~/LCTHW/Lectures/ex24 (git)-[master] % ./ex24
What's your First Name? dddddddddddddddddddddddddddddddddddddddddddddddddddddd
[ERROR] (ex24.c:39: errno: None) You have to enter a number.
What's your Last Name? How old are you? %     
bruda@cyborg ~/LCTHW/Lectures/ex24 (git)-[master] %

MY gcc -v:

Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-pc-linux-gnu/8.2.1/lto-wrapper
Target: x86_64-pc-linux-gnu
Configured with: /build/gcc/src/gcc/configure --prefix=/usr --libdir=/usr/lib --libexecdir=/usr/lib --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=https://bugs.archlinux.org/ --enable-languages=c,c++,ada,fortran,go,lto,objc,obj-c++ --enable-shared --enable-threads=posix --enable-libmpx --with-system-zlib --with-isl --enable-__cxa_atexit --disable-libunwind-exceptions --enable-clocale=gnu --disable-libstdcxx-pch --disable-libssp --enable-gnu-unique-object --enable-linker-build-id --enable-lto --enable-plugin --enable-install-libiberty --with-linker-hash-style=gnu --enable-gnu-indirect-function --enable-multilib --disable-werror --enable-checking=release --enable-default-pie --enable-default-ssp --enable-cet=auto
Thread model: posix
gcc version 8.2.1 20180831 (GCC) 

Yeah, I meant Ex24 with the max data. Edited

According to gdb rc == 0 when it gets to 39. That’s after input was entered.

1 Like

I feel like it’s just something with STDIN. Flushing the buffer by repeatedly getting characters seemed to work.

What’s the value of OTHER_EYES supposed to be when it gets to this point?

4, it’s in the enum at the top.

1 Like

Yea it seems like one of those mysterious C things that remind us of why it’s such a terrible language lol. But we get better as we learn more ways it can mess up. I haven’t gotten to this exercise yet. But overall everything seems clean & clear in zeds code. I get the same bug you do it seems.

1 Like

Yeah… I agree. On the other hand, C is super fast and cross-platform. It’s always interesting finding ways to mess it up, for me it’s the same with other programming languages.

1 Like

That’s the results I get, so I have no idea why @archmaster is getting a crash. Sooooooooooooooo weird. I’m sure there’s some bizarre edge case I don’t know about (since that’s C), but I would suggest that you “fix” fgets by telling it to read MAX_DATA-1 or have your arrays sized at MAX_DATA+1 but tell fgets MAX_DATA. That way there’s always a final \0 (if you calloc or set to 0} and it’ll at least not overrun the end.

Now the next thing is if you tell fgets to read 10, and then you blast it with 100, then the 90 remaining get queued up. That’s why we’re seeing that @lee8oi has here. That’s not really a bug at all.

The second thing you said with the characters queuing up is what I think the problem with mine was. I’m sorry if I wasn’t communicating clearly.

My flush_stdin() function seemed to resolve that, so all’s good. Thanks so much for your help!

I’m on ex24 now and I can see this weird behavior. It seems that stdin from the linux terminals queue up the characters you type and continue to feed them into the the program so the subsequent reads with fgets continue to receive the input until limits for each one were filled. Even without waiting for an enter key I’m assuming BECAUSE the input was maxed. When it filled the number with text it caused a crash. Bad number…it’s text. Interesting?

I found a solution using __fpurge from stdio_ext.h (supported) on stdin.

#include <stdio.h>
#include <stdio_ext.h>
#include "dbg.h"

#define MAX_DATA 100

typedef enum EyeColor {
    BLUE_EYES, GREEN_EYES, BROWN_EYES,
    BLACK_EYES, OTHER_EYES
} EyeColor;

const char *EYE_COLOR_NAMES[] = {
    "Blue", "Green", "Brown", "Black", "Other"
};

typedef struct Person {
    int age;
    char first_name[MAX_DATA];
    char last_name[MAX_DATA];
    EyeColor eyes;
    float income;
} Person;

int Read_string(char *dest, char *pretext, char *errmsg)
{
    printf(pretext);
    char *in = NULL;
    in = fgets(dest, MAX_DATA, stdin);
    if (in == NULL) { 
        fprintf(stderr, errmsg);
        return -1; 
    }
    __fpurge(stdin);
    return 0;
}

int main(int argc, char *argv[])
{
    Person you = {.age = 0 };
    int i = 0;

    Read_string(you.first_name, "What's your first name? ", "Failed to read first name.");

    Read_string(you.last_name, "What's your last name? ", "Failed to read last name.");

    printf("How old are you? ");
    int rc = fscanf(stdin, "%d", &you.age);
    check(rc > 0, "You have to enter a number.");

    printf("What color are your eyes:\n");
    for (i = 0; i < OTHER_EYES; i++) {
        printf("%d) %s\n", i + 1, EYE_COLOR_NAMES[i]);
    }
    printf("> ");

    int eyes = -1;
    rc = fscanf(stdin, "%d", &eyes);
    check(rc > 0, "You have to enter a number.");
    
    you.eyes = eyes - 1;
    check(you.eyes <= OTHER_EYES
        && you.eyes >= 0, "Do it right, that's not an option.");

    printf("How much do you make an hour? ");
    rc = fscanf(stdin, "%f", &you.income);
    check(rc > 0, "Enter a floating point number.");

    printf("----- RESULTS -----\n");
    printf("First Name: %s\n", you.first_name);
    printf("Last Name: %s\n", you.last_name);
    printf("Age: %d\n", you.age);
    printf("Eyes: %s\n", EYE_COLOR_NAMES[you.eyes]);
    printf("Income: %f\n", you.income);

    return 0;

error:
    return -1;
}
1 Like

That looks much better than my janky solution! I didn’t realize that existed; I’ll totally modify my code now. :smiley:

I like it better too because it’s easy enough to add to the existing code. Simply __fpurge(stdio); after reading input with fgets/etc. It’ll purge the standard input stream and the next read starts fresh. I also don’t think it’s necessary to do MAX_DATA - 1 in fgets since it already does size - 1 and closes the stream with ‘\0’. My solution shows that it works.

On a side note I did the extra credit and scanf didn’t need the stdin flush. And "%s%n" format seems to grab a nice tidy string without the \n at the end.

ex24.c:Scan_string()
int Scan_string(char *dest)
{
    int nothing;
    int n = scanf("%s%n", dest, &nothing); 
    check(n > 0, "Failed to scan string.");
    dest[MAX_DATA - 1] = '\0';
    return 0;

error:
    return -1;
}
ex24.c:Scan_integer()
int Scan_integer(int *dest)
{
    int nothing;
    int n = scanf("%d%n", dest, &nothing); 
    check(n > 0, "Failed to scan integer.");

    return 0;

error:
    return -1;
}
ex24.c:Scan_float()
int Scan_float(float *dest)
{
    int nothing;
    int n = scanf("%f%n", dest, &nothing); 
    check(n > 0, "Failed to scan float.");

    return 0;

error:
    return -1;
}
1 Like

I wonder why fgets doesn’t flush stdin… still, great find! I’m curious, how did you do the expandable thingies?

Talk to discobot about the advanced user tutorial. If you haven’t already covered the basic one. He shows you how to work with the forum’s features. Including that ‘expandable thingie’. I discovered that last night. Even got a badge for completing it :slight_smile:

A few useful discobot commands to try:

@discobot display help
@discobot start new user
@discobot start advanced user

Actually I believe fgets is doing exactly what it’s supposed to. It reads up to so many characters and moves on. I don’t think it’s part of the job to flush the stdin. When the shell is programmed to keep feeding the input into the program, fgets programmed to read up to so many bytes and just leaves the stream as-is for the program to continue handling the provided input, the additional characters continue to push into the program while the subsequent calls to fgets continue to read the input. Thus filling up the variables similar to a input loop that keeps reading things in until the program itself somehow determines that enough input has been obtained. Which in the case of the original ex24.c code the number check was the first thing to realize something was amiss when it was receiving string input instead of numbers.