10

If I type the words "Hello World" into the standard input stream, this program will print out weird box symbols instead of the expected "Hello World" back into standard output.

#include <stdio.h>

int main(void)
{
    // print out all characters from the stream until '/n' character is found
    int ch;
    while (ch = getchar() != '\n')
    {
        putchar(ch);
    }
    putchar('\n');
}

I am aware of how to fix the problem. But why is this line of code incorrect?

while (ch = getchar() != '\n')
Sourav Ghosh
  • 133,132
  • 16
  • 183
  • 261
zxgear
  • 1,168
  • 14
  • 30
  • 1
    You can find a list of operator precedences here by the way: http://en.cppreference.com/w/c/language/operator_precedence. (Felt a bit silly posting an answer with the million that just came in. :D) – Ulfalizer Mar 27 '15 at 07:17
  • Interesting, I did not think that the operator precedence in C would differ from C# and Java. – zxgear Mar 27 '15 at 07:36
  • If you're thinking of this particular case, then it seems to be the same in Java and C#. It would be bad if `x_eq_y = x == y` was interpreted as `(x_eq_y = x) == y`, as a silly example. C has some wonky precedences though (which have been acknowledged by the authors as being a mistake, but are still emulated by other languages for compatibility). For example, `x == y << z` is the same as `x == (y << z)` as you'd expect, while `x == y & z` is the same as `(x == y) & z`. – Ulfalizer Mar 27 '15 at 07:49
  • Try this in Java: (fooBoolean = fooInteger == barInteger). The == operator has higher precedence than the = operator. – zxgear Mar 27 '15 at 08:09
  • 1
    @JohnH In any case, operator precedence in Java and C# differ from C's. Not the other way round ('cause C is older). Additionally in order to never fail again in precedence in C, you must remember that multiplication and division have higher precedence than addition and subtraction. Everything else needs parenthesis. – gon1332 Mar 27 '15 at 11:26
  • IMO this is exactly why the "single statement -> single responsibility -> single action" principle was created... so that people won't have a `WTF operator precedence` every 5 minutes. Either separate compound statements or use explicit parentheses. In any other case you're asking for trouble or forcing it upon other people. –  Mar 27 '15 at 19:43

4 Answers4

30

(ch = getchar() != '\n') should be rewritten as

((ch = getchar()) != '\n')

Because != binds tighter than = in C operator precedence table. Operator are not ordered from left to right (reading direction of english) as one might expect. For example result of 2 + 3 * 5 is 17 and not 25. This is because * will be performed before performing +, because * operator has more precedence than + operator.

So when you write something like

ch = getchar() != '\n'

You expect it to be equivalent to: (ch = getchar()) != '\n'

But actually it is equivalent to: ch = (getchar() != '\n')

Because the result of != is either true or false, you see character \001 on screen. I believe \001 appears as boxes1 on your system.


1: Character \001 may appear as a box or dot or some wierd character or it may not appear in output at all.

Cœur
  • 37,241
  • 25
  • 195
  • 267
Mohit Jain
  • 30,259
  • 8
  • 73
  • 100
  • In reference to `For example result of 2 + 3 * 5 is 17 and not 25`....This is a great answer but, Had it been `2 * 3 + 5`, it would still be evaluated as `11` and not as `16`.. It has got to do with the `*` operator precedence over `+` than Right to Left evaluation. Some rookie would take this wrongly.\ – WedaPashi Mar 27 '15 at 10:55
  • @WedaPashi Thanks for your helpful comment, I will update my answer accordingly. – Mohit Jain Mar 27 '15 at 11:17
  • 1
    By the program logic, \000 will be seen "never", not "rarely". As soon as gethcar() returns '\n', ch is assigned 0 and the loob body is not executed. – Hagen von Eitzen Mar 27 '15 at 19:16
12

And as a slightly meta-ish answer, the overarching fix is always compiling with warnings enabled:

$ gcc t.c -Wall
t.c: In function ‘main’:
t.c:7:5: warning: suggest parentheses around assignment used as truth value [-Wparentheses]
     while (ch = getchar() != '\n')
     ^
t.c:12:1: warning: control reaches end of non-void function [-Wreturn-type]
 }
 ^

Or better yet try clang, which warns by default and generally gives better diagnostic messages:

$ clang t.c
t.c:7:15: warning: using the result of an assignment as a condition without parentheses [-Wparentheses]
    while (ch = getchar() != '\n')
           ~~~^~~~~~~~~~~~~~~~~~~
t.c:7:15: note: place parentheses around the assignment to silence this warning
    while (ch = getchar() != '\n')
          ^
           (                     )
t.c:7:15: note: use '==' to turn this assignment into an equality comparison
    while (ch = getchar() != '\n')
              ^
              ==
1 warning generated.
Tom Goodfellow
  • 882
  • 8
  • 18
8

You need to be aware of operator precedence - comparison operators such as != have a higher precedence than assignment (=). Use parentheses to enforce the required behaviour, i.e. change:

while (ch = getchar() != '\n')

to:

while ((ch = getchar()) != '\n')


Addendum: be sure to take heed of the advice from @TomGoodfellow in a separate answer below - using a decent compiler with warnings enabled (e.g. gcc -Wall) would have alerted you to this problem immediately.
Paul R
  • 208,748
  • 37
  • 389
  • 560
5

Because you need to write it as while ((ch = getchar()) != '\n')

Yasir Majeed
  • 739
  • 3
  • 12
  • 1
    While you're very right, you should add some description about what, how and why in your answer to make it a great one. Cheers !! :-) – Sourav Ghosh Mar 27 '15 at 07:24