2
votes

I am having trouble with the stdin buffer, I'd appreciate any insight, I have this function which accepts user input for last name

void lastName(int *counter, User *pt) {

    for (int i = *counter; i < (*counter + 1); i++) {
        pt[i].lastName = calloc (MAX_LENGTH, sizeof(char));
        printf("Enter Last Name: ");
        fgets(pt[i].lastName, MAX_LENGTH, stdin);
        strtok(pt[i].lastName, "\n");
    }
}

I also have this function that accepts user input for ID

void id(int *counter, User *pt) {

    char num[MAX_LENGTH];
    long temp;
    for (int i = *counter; i < *counter + 1; i++) {
        printf("Enter the ID of %s: ", pt[i].firstName);
        fgets(num, MAX_LENGTH, stdin);
        strtok(num, "\n");
        temp = strtol(num, NULL, 10);
        pt[i].id = (int) temp;
    }
}

This is how I am calling them in main

lastName(&counter, pt);
id(&counter, pt);

If i enter a last name which is very long, it gets cut and displayed to MAX_LENGTH but the fgets for the ID gets skipped, otherwise it works fine. I am wondering as to how my fgets is working in this case? MAX_LENGTH is 10. I tried clearing my buffer with while(getchar() != '\n'); and it works but I have to press enter twice.

Enter First Name: Test
Enter Last Name: Williamsamsmases
Enter the ID of Test: Would you like to enter a User?(Y/N):N
2
It's not getting skipped. It is eating up whatever the previous call haven't finished.Eugene Sh.
If you enter a line that is longer than what the destination buffer can hold, then fgets will read everything it can until the destination is full. The rest of the input remains in the input buffer and, so the next fgets call reads what has been left behind.Pablo
Do you recommend something to clear the buffer with? fflush(stdin) doesn't seem to work for me, neither does fseek(stdin, 0, SEEK_END)Kevin H
fflush(stdin) is undefined behavior. You can getchar until \n encountered. stackoverflow.com/questions/7898215/…Eugene Sh.
stdin is not a seek-able stream, you cannot use fseek with stdinPablo

2 Answers

2
votes

If you enter a line that is longer than what the destination buffer can hold, then fgets will read everything it can until the destination is full. The rest of the input remains in the input buffer and, so the next fgets call reads what has been left behind.

If a newline is not present in the destination buffer, you have 3 options:

  1. the entered line is longer than what the destination can hold. In that case you have to keep calling fgets until a newline is in the buffer.
  2. You can "clean" the input buffer with this function:

    void clean_stdin(void)
    {
        int c;
        while((c = getchar()) != '\n' && c != EOF);
    }
    

    and then you can call it when you realize that there is no newline in the destination buffer. But don't call it every time you call fgets, you can only call this function when you know that there are characters left in the input buffer (when there is no newline in the destination buffer), otherwise the function will wait for user input.

  3. If your system has support for getline, then you can use it to read the whole line and getline will take care of allocating the memory needed for it:

    char *line = NULL;
    size_t n = 0;
    
    if(getline(&line, &n, stdin) == -1)
    {
        // error
    }
    
    ...
    free(line);
    

    If getline is not available on your system, then you could write a function that emulates getline, one like this:

    char *fgets_long(FILE *fp)
    {
        size_t size = 0, currlen = 0;
        char line[1024];
        char *ret = NULL, *tmp;
    
        while(fgets(line, sizeof line, fp))
        {
            int wholeline = 0;
            size_t len = strlen(line);
    
            if(line[len - 1] == '\n')
            {
                line[len-- - 1] = 0;
                wholeline = 1;
            }
    
            if(currlen + len >= size)
            {
                // we need more space in the buffer
                size += (sizeof line) - (size ? 1 : 0);
                tmp = realloc(ret, size);
                if(tmp == NULL)
                    break; // return all we've got so far
                ret = tmp;
            }
    
            memcpy(ret + currlen, line, len + 1);
            currlen += len;
    
            if(wholeline)
                break;
        }
    
        if(ret)
        {
            tmp = realloc(ret, currlen + 1);
            if(tmp)
                ret = tmp;
        }
    
        return ret;
    }
    

    This function works like fgets but it guarantees that you read the whole line, the function takes care of allocation enough memory for the destination.

1
votes

When you call fgets, it'll read input from stdin until the variable being inserted into is full. Once it is, the rest of the input on the input buffer remains there. One solution is to clear the input buffer after reading each value.

int ch;

while ((ch = getchar()) != '\n' && ch != EOF);

You essentially read from the buffer and discard the values until a \n or EOF is reached.