See ctype(3) and search for CAVEATS: CAVEATS The argument of these functions is of type int, but only a very restricted subset of values are actually valid. The argument must either be the value of the macro EOF (which has a negative value), or must be a non-negative value within the range representable as unsigned char. Passing invalid values leads to undefined behavior.
Clearly casting EOF to unsigned char will give an invalid input value to any ctype function. The whole interface is ... suboptimal, but there is nothing we can do about it. You need to first test for EOF but then cast any non-EOF characters to unsigned char to be portable: for (;;) { int c = fgetc(c); if (c == EOF || !isprint((unsigned char)c)) break; } This, unfortunately, voids the reasoning (or "benefits") why EOF as int was allowed to be passed to ctype functions in the first place. Luckily the main usage nowadays is with in-memory strings and there things are simpler (as explained in that CAVEAT section). Martin