0

Trying to understand the behavior of my code. I'm expecting Ctrl-D to lead to the program printing the array and exiting, however it takes 3 presses, and it enters the while loop after the second press.

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

void unyon(int p, int q);
int connected(int p, int q);

int main(int argc, char *argv[]) {
    int c, p, q, i, size, *ptr;

    scanf("%d", &size);

    ptr = malloc(size * sizeof(int));

    while((c = getchar()) != EOF){
        scanf("%d", &p);
        scanf("%d", &q);

        printf("p = %d, q = %d\n", p, q);
    }

    for(i = 0; i < size; ++i)
        printf("%d\n", *ptr + i);

    free(ptr);
    return 0;
}

I read the post here, but I don't quite understand it. How to end scanf by entering only one EOF

After reading that, I'm expecting the first Ctrl-D to clear the buffer, and then I'm expecting c = getchar() to pick up the second Ctrl-D and jump out. Instead the second Ctrl-D enters the loop and prints p and q, and it takes a third Ctrl-D to drop out.

This is made more confusing by the fact that the code below drops out on the first Ctrl-D-

#include <stdio.h>

main() {

    int c, nl;

    nl = 0;
    while((c = getchar()) != EOF)
        if (c == '\n')
            ++nl;
    printf("%d\n", nl);
}
Community
  • 1
  • 1
gr0k
  • 789
  • 2
  • 9
  • 22
  • 1
    You are not checking the return values from `scanf`. If your stream is errored, I don't think you can reasonably expect it to give you the same error back. – paddy Sep 01 '15 at 03:16
  • @paddy: You can actually reasonably expect that, but if you are using glibc your expectation will not be fulfilled. – rici Sep 01 '15 at 03:37
  • 1
    A related note: always check return values of `scanf` family of functions! And read the docs to see what the value actually means. – hyde Sep 01 '15 at 04:36

3 Answers3

4

Let's just strip the program down to the calls which do input:

scanf("%d", &size);             // Statement 1
while((c = getchar()) != EOF){  //           2
    scanf("%d", &p);            //           3
    scanf("%d", &q);            //           4
}

That is definitely not the way to go; we'll get to the correct usage in a bit. For now, let's just analyze what happens. It's important to understand precisely how scanf works. The %d format code causes it to first skip over any whitespace characters, and then read characters as long as the characters can be made into a decimal integer. Eventually some character will be read which is not part of a decimal integer; most likely a newline character. Because the format string is now finished, the unused character which has just been read will be reinserted into the stream.

So when the call to getchar is made, getchar will read and return the newline character which terminated the integer. Inside the loop, there are then two calls to scanf("%d"), each of which will behave as indicated above: skip whitespace if any, read a decimal integer, and reinsert the unused character back into the input stream.

Now, let's suppose that you run the program, and enter the number 42 followed by the enter key, and then Ctrl-D to close the input stream.

The 42 will be read by statement 1, and (as mentioned above) the newline will be read by statement 2. So when statement 3 is executed, there is no more data to be read. Because end-of-file is signaled before any digit is read, scanf will return EOF. However, the code does not test the return value of scanf; it goes on to statement 4.

What should happen at this point is that the scanf in statement 4 should immediately return EOF without attempting to read more input. That's what the C standard says should happen, and it is what Posix says should happen. Once end-of-file has been signaled on a stream, any input request should immediately return EOF until the end-of-file indicator is manually cleared. (See below for standards quotes.)

But glibc, for reasons we won't go into just yet, does not conform to the standard. It attempts another read. And so the user must enter another Ctrl-D, which will cause the scanf at statement 4 to return EOF. Again, the code does not check the return code, so it continues with the while loop and calls getchar again at statement 2. Because of the same bug, getchar does not immediately return EOF, but instead attempts to read a character from the terminal. So the user must now type a third Ctrl-D to cause getchar to return EOF. Finally, the code checks a return code, and the while loop terminates.


So that is the explanation of what is happening. Now, it is easy to see at least one mistake in the code: the return value of scanf is never checked. Not only does this mean that EOF is missed, it also means that input errors are ignored. (scanf would have returned 0 if the input could not be parsed as an integer.) That's serious, because if scanf cannot succesfully match the format code, the value of the corresponding argument is undefined and must not be used.

In short: Always check return values from *scanf. (And other I/O library functions.)

But there is a more subtle mistake as well, which makes little difference in this case but could, in general, be serious. The character read by getchar in statement 2 is simply discarded, regardless of what it was. Normally it will be whitespace, so it doesn't matter that it is discarded, but you don't actually know that because the character is discarded. Maybe it was a comma. Maybe it was a letter. Maybe it matters what it was.

It is bad style to rely on the assumption that whatever character is read by the getchar at statement 2 is unimportant. If you really need to peek at the next character, you should reinsert it into the input stream, just as scanf does:

while ((c = getchar()) != EOF) {
  ungetc(c, stdin);  /* Put c back into the input stream */
  ...
}

But actually, that test is not what you want at all. As we have already seen, it is extremely unlikely that getchar will return EOF at this point. (It's possible, but it's very unlikely). Much more more probable is that getchar will read a newline character, even though the next scanf will encounter the end-of-file. So there was absolutely no point peeking at the next character; the correct solution is to check the return code of scanf, as indicated above.

Putting that together, what you really want here is something more like:

/* No reason to use two scanf calls to read two consecutive numbers */
while ((count = scanf("%d%d", &p, &q)) == 2) {
  /* Do something with p and q */
}
if (count != EOF) {
  /* Invalid format. Issue an error message, at least */
}
/* Do whatever needs to be done at the end of input. */

Finally, let's examine glibc's behaviour. There is a very long-standing bug report linked to by an answer to the question cited in the OP. If you take the trouble to read through to the most recent post in the bugzilla thread, you'll find a link to a discussion on the glibc developer mailing list.

Let me give the TL;DR version, and save you the trouble of digital archaeology. Since C99, the standard has been clear that EOF is "sticky". §7.21.3/11 states that all input is performed as though successive bytes were read by fgetc:

...The byte input functions read characters from the stream as if by successive calls to the fgetc function.

And §7.21.7.1/3 states that fgetc returns EOF immediately if the stream's end-of-file indicator is set:

If the end-of-file indicator for the stream is set, or if the stream is at end-of-file, the end-of-file indicator for the stream is set and the fgetc function returns EOF. Otherwise, the fgetc function returns the next character from the input stream pointed to by stream. If a read error occurs, the error indicator for the stream is set and the fgetc function returns EOF.

So once the end-of-file indicator is set, because either end of file was detected or some read error occurred, subsequent input operations must immediately return EOF without attempting to read from the stream. Various things can clear the end-of-file indicator, including clearerr, seek, and ungetc; once the end-of-file indicator has been cleared, the next input function call will again attempt to read from the stream.

However, it wasn't always like that. Before C99, the result of reading from a stream which had already returned EOF was unspecified. And different standard libraries chose to handle it in different ways.

So a decision was made to not change glibc to conform to the (then) new standard, but rather to maintain compatibility with certain other C libraries, notably Solaris. (A comment in the glibc source is quoted in the bug report.)

Although there is a compelling argument (at least, compelling to me) that fixing the bug is not likely to break anything important, there is still a certain reluctance to do anything about it. And so, here we are, ten years later, with a still-open bug report, and a non-conforming implementation.

Community
  • 1
  • 1
rici
  • 234,347
  • 28
  • 237
  • 341
  • I know it says "avoid thank yous" in the comment box, but an answer like that warrants a huge thank you and I can't send a PM. I wasnt expecting such an awesome and comprehensive answer. I'm still reading through it to get a full understanding, I'll post up some follow up questions when I get through all that. – gr0k Sep 01 '15 at 12:20
  • Follow up question- You mentioned the unused character gets reinserted into the stream. If I enter 10^D, I'd expect 10 to get read by statement 1 and ^D to get reinserted to the stream. I'd then expect getchar() to read that ^D off the stream, which isn't the case. I then figured a second ^D would skip the while loop but instead, it still takes a third ^D to exit out. The 3x^D with \n makes sense to me, but 10^D^D^D input is clear as mud. – gr0k Sep 01 '15 at 22:43
  • 2
    @SamThode: Ctrl-D never gets into the stream. What happens when you type a control-D depends on whether or not it is the first character in a line. If not, it simply terminates the current read operation. At the beginning of a line, it also causes an end-of-file indication on the input stream. Note that you cannot reinsert `EOF` because `EOF` is not a character; it is a value returned by `fgetc` when the stream is at end-of-file. (It shouldn't be necessary to reinsert `EOF` because end-of-file is supposed to be sticky. Hence the complaint about the glibc bug.) – rici Sep 01 '15 at 23:04
  • 1
    @SamThode: Note that `scanf` continues to read until its format string has been exhausted or it cannot match a format item. If you type `10^D^D`, the first control-D simply terminates the current read operation, but `scanf` issues another read since no end-of-file was signalled and it hasn't finished matching the `%d` format code yet. The second control-D is at the beginning of an input, so it does signal end-of-file, and that causes the `scanf` to terminate. However, `scanf` has finished it's format string so it doesn't return `EOF`; instead, it returns the number of values it scanned... – rici Sep 01 '15 at 23:13
  • 1
    @SamThode: That's another reason the bug in `glibc` is a pain. If end-of-file were sticky, the next `scanf` would correctly return `EOF`. (Fortunately, people hardly ever use control-D in this fashion. If they do, they are almost always confused about the result.) Also, see http://stackoverflow.com/a/30690100/1566221. Finally, this really deserves to be a different question. :) – rici Sep 01 '15 at 23:15
1

If you run it through the debugger you will get a clearer picture. Here is the sequence of events.

  1. scanf("%d", &size); is called.
  2. A number is input followed by ENTER. The key here is that scanf does not consume the \n that results from the ENTER.
  3. getchar is called. This consumes the \n.
  4. scanf("%d", &p); is called. This consumes the first ctrl-D. If the return value were checked then it would be apparent that an error occured.
  5. scanf("%d", &q); is called. This consumes the second ctrl-D.
  6. Loop goes back to the top and calls getchar. The third ctrl-D then causes EOF to be returned by getchar and hence the loop breaks out at that point.

I'll leave it as an exercise for you to explain why the second program functions as expected.

kaylum
  • 13,833
  • 2
  • 22
  • 31
0

There are different things messing here.

First of all, when you type Ctrl-D to the input terminal, the tty driver is processing your input, adding each character in a buffer and processing special characters. One of these special characters (Ctrl-D) means take up to the last char and make them all available to the system. This makes two things to happen: first, the Ctrl-D character is eliminated from the data stream and; second, all the characters typed up so far are made available to be read(2) by the process syscall. getchar() is a buffered library call that avoids making one read per character, allowing to store previously read characters in the buffer.

Other thing messing here is the way the system signals the end of file in posix systems (and all unix systems). When you make a read(2) system call, the return value is the actual number of characters read (or -1 in case of failure, but this has nothing to do with EOF, as will be explained soon). And the system marks the end of file condition by returning 0 characters. So, the operating system marks the end of file making read(2) return 0 bytes as a result (if you only hit the return key, that will make a \n to appear in the data stream).

The third thing messing up here is the type of return value from getchar(3) function. It doesn't return a char value. As all possible byte values are posible to be returned for getchar(3), there's no possibility to reserve a special value for signalling a EOF. The solution adopted a long, long, time ago (when getchar(3) was designed, that is in the first version of the C language, (see The C programming language by Brian Kernighan and Denis Ritchie, first ed.) was to use an int as return value to be able to return all the possible byte values (0..255) plus one extra value, called EOF. The exact value of EOF is implementation dependant, but normally defined as -1 (I think even the standard specifies now it must be defined as -1, but not sure)

So, making all things work together, EOF is an int constant defined to allow programers to write while ((c = getchar()) != EOF). You will never get -1 as a data value from the terminal. The system always marks the end of file condition by making read(2) to return 0. And the terminal driver on receiving Ctrl-D just eliminates it from the stream and makes data up to, but not including (as different from Ctrl-J or Ctrl-M, line feed and carry return, respectivelly, that are also interpreted and are input as \n in the data stream)

So, next the question is: Why there are needed normally two (or more) Ctrl-D chars to signal eof?

Right, as I've said, one only makes all thata up to the Ctrl-D (but not including it) available to the kernel, so the result from read(2) can be a number different than 0 for the first time. But what is sure is that if you enter the Ctrl-D char twice in sequence, after the first there were not be more chars in between the two chars, assuring a read() of zero chars. Normally, programs are in a loop, doing multiple reads

while ((n_read = read(fd, buffer, sizeof buffer)) > 0) {
    /* NORMAL INPUT PROCESSING GOES HERE, for up to n_read bytes
     * stored in buffer */
} /* while */
if (n_read < 0) {
    /* ERROR PROCESSING GOES HERE */
} else {
    /* EOF PROCESSING GOES HERE */
} /* if */

In the case of files, the behaviour is different, as Ctrl-D is not interpreted by any driver (it's stored in the disk file) so you'll get Ctrl-D as a normal character (it's value is \004)

When you read a file, normally this deals to reading a lot of complete buffers, then make a partial read (with less than the buffer size bytes input) and a final read of zero bytes, signalling that the file has ended.

Note

Depending on the configuration of the tty driver in some unices, the eof character can be changed and have different mean. Also happens to the return character and linefeed character. Se termios(3) manual page for a detailed documentation on this.

Community
  • 1
  • 1
Luis Colorado
  • 10,974
  • 1
  • 16
  • 31