5
\$\begingroup\$

I'm working through K&R at the moment and I've found my first exercise that seems to be a good use for my first CodeReview post.

Exercise 1-6 - Verify that the expression getchar() != EOF is 0 or 1.

Exercise 1-7 - Write a program to print the value of EOF.

I've completed both of these together in the same file.

I think I've understood the question here, but let me know if I haven't. I wanted to post here before looking up any sort of "official" answer so that I could learn some stuff instead of just "going to the back of the book".

#include <stdio.h>

void main()
{
        int c;

        c = getchar();
        while ((c = getchar()) != EOF == (0 | 1)) {
                putchar(c);
        }
        printf("%d\n", EOF);
}

Compiled with:

gcc putchar.c -o putchar

Sending EOF using:

<ctrl-d>

Runs through fine, and when I ctrl-d I get a -1 return code.

\$\endgroup\$
  • 7
    \$\begingroup\$ I don't see the exercise but (0 | 1) isn't probably what you think it is. | is bitwise OR then it's equivalent to 1. Now it then can be reduced to getchar() != EOF == 1 which is again equivalent to getchar() != EOF (because == returns 1 or 0 and it's mandated by the standard). \$\endgroup\$ – Adriano Repetti Jul 12 '18 at 16:15
  • \$\begingroup\$ Ahhhhh right yeah I've definitely misused it then! Cheers for that, I'll have a look over my code and make an edit! Thanks. \$\endgroup\$ – John Von Neumann Jul 12 '18 at 16:28
  • 2
    \$\begingroup\$ I think (but I may be wrong) that someone considers the question off-topic because code does not work as intended. I don't agree because close reason says (emphasis is mine) "...working correctly, to the best of the author's knowledge" then this is a perfectly fine question. \$\endgroup\$ – Adriano Repetti Jul 12 '18 at 16:40
  • 2
    \$\begingroup\$ @AdrianoRepetti While that may have been true at the time of posting, it is no longer true (as the OP is now aware of the bug). It is also what might be considered an obvious bug (at least obvious after having encountered it many times before). OP, you can (almost) always edit your question to make it on-topic, once you have the correct solution. The only exception to that is if you have received answer (so as not to invalidate them). \$\endgroup\$ – Graipher Jul 12 '18 at 17:23
  • 3
    \$\begingroup\$ @Graipher The code works, just not for the reason that the author thought. That misconception is perfect material to be discussed in a Code Review answer — no need to edit the question. \$\endgroup\$ – 200_success Jul 12 '18 at 18:39
4
\$\begingroup\$

This is a nice, clear, short attempt at the exercise.

You've avoided a couple of common problems:

  • You're correctly using int for the return value from getchar(). A common mistake is to be misled by the name and to assign directly to a char variable. That doesn't work, because truncating to char prevents EOF from being distinguished from a valid char value.
  • You've correctly matched the %d specifier to the type of EOF.

One simple way to improve your code is to get the compiler to point out weaknesses for you (by default, most C compilers are very lenient; you have to specifically ask for more warnings):

gcc -std=c11 -Wall -Wextra -Wwrite-strings -Wno-parentheses -Wpedantic -Warray-bounds -Wconversion putchar.c -o putchar

You can make it easier by writing a simple Makefile that contains just:

CFLAGS = -std=c11 -Wall -Wextra -Wwrite-strings -Wno-parentheses -Wpedantic -Warray-bounds -Wconversion

And you can then build simply with

make putchar

When you've enabled warnings, you'll see:

198374.c:3:6: warning: return type of ‘main’ is not ‘int’ [-Wmain]
 void main()
      ^~~~

The fix for that should be obvious.


Now to the meat of the program. Firstly, before the loop we have a statement

    c = getchar();

This consumes and ignores one character. We probably didn't mean to throw that one away.


Now consider this expression:

 (c = getchar()) != EOF == (0 | 1)

We're actually comparing (after the assignment) c != EOF against 0|1. But | is the bitwise-or operator, so 0|1 evaluates to just 1. When c is not EOF, then c!=EOF is 1, and the condition is satisfied. The last time around the loop (when c becomes EOF), then c!=EOF is 0, and the loop exits.

What we want to do is save the result of c != EOF and compare that to 0 and to 1 separately. Or, even more simply for this exercise, just print it to standard output:

    do {
        c = getchar();
        printf("getchar() != EOF: %d\n", c != EOF);
    } while (c != EOF);

Note: given that we haven't been asked to copy the input to output, we shouldn't have the putchar() call.

If we really want to prove that c != EOF is always either 0 or 1, we could test it automatically:

    do {
        c = getchar();
        if ((c != EOF) != 0  &&  (c != EOF) != 1) {
            fprintf(stderr, "(c != EOF) was neither 0 nor 1\n");
            return 1;
        }
    } while (c != EOF);

You might choose to use a switch statement instead of the if; I think that's clearer:

        c = getchar();
        switch (c != EOF) {
        case 0:             /* okay */
        case 1:             /* okay */
            break;
        default:
            fprintf(stderr, "(c != EOF) was neither 0 nor 1\n");
            return 1;
        }

Modified code

#include <stdio.h>

int main()
{
        int c;

        do {
            c = getchar();
            switch (c != EOF) {
            case 0:             /* okay */
            case 1:             /* okay */
                break;
            default:
                fprintf(stderr, "(c != EOF) was neither 0 nor 1\n");
                return 1;
            }
        } while (c != EOF);

        printf("%d\n", c);
}
\$\endgroup\$
  • \$\begingroup\$ Cheers for the awesome reply! Means a lot, and the Makefile stuff I had no clue about, thanks for that, works like a charm. The c = getchar() throwing away the first character I didn't know about, but I had noticed that it was happening, had no clue why though! \$\endgroup\$ – John Von Neumann Jul 13 '18 at 14:33
  • \$\begingroup\$ "That doesn't work, becauseEOF is outside the range of any valid char." --> Not quite. EOF often has the value of -1 and a signed char typically has the range of -128 to 127. So having an EOF inside the char range is common. The "doesn't work" is due to getchar() retuning values in the unsigned char range and the negative EOF and these typical 257 different values are not distinguishable with an 8-bit char. \$\endgroup\$ – chux Jul 13 '18 at 14:36
  • \$\begingroup\$ Oh yes - I hate these conversions between plain and unsigned char (such as with the to*() family of functions). EOF does have to have a negative value, and I mistakenly assumed that it just had to be more negative than CHAR_MIN. \$\endgroup\$ – Toby Speight Jul 13 '18 at 14:49
  • \$\begingroup\$ Why do we do this? ` case 0: /* okay / case 1: / okay */` And not assign a success for the case statements? Except for the 1 of course. \$\endgroup\$ – John Von Neumann Jul 13 '18 at 15:03
  • \$\begingroup\$ EDIT: Ignore me, I know why now haha. Because 0 is success so we just continue checking until EOF or an issue, hence the break on exit code 1. \$\endgroup\$ – John Von Neumann Jul 13 '18 at 15:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.