-1
printf("%c %c", getc(stdin), getc(stdin));
rewind(stdin); printf("\n");
printf("%c %c", getchar(), getchar());

output
s

 s
s

 s

I wrote the source code like that and I pressed s, enter, s, enter sequentially on windows console application using vs2017. Console shows me what I put and print output when I press the key. The result's appearance is like s, enter, enter, space, s, s, enter, enter, space, s. Did I use the function in wrong way?

Q1. I pressed s, enter sequentially, but why output is reversed?
Q2. At output's third line and sixth line, why is there exist whitespace like  s not s?.

lev.1 code
  • 87
  • 7
  • 1
    Printing with `%d` should not produce `s` or a blank as output. Show the real code! The problem is likely that the order in which the arguments to a function are evaluated is up to the compiler. It doesn’t even have to be self-consistent — the two calls to `printf` may have the arguments evaluated in different orders. – Jonathan Leffler Feb 19 '20 at 13:25
  • 1
    Also, you can’t rewind a terminal; it is a non-seekable device. – Jonathan Leffler Feb 19 '20 at 13:26
  • @Jonathan Leffler I misused function type specifier, edited my post. Reading your answer, I understood output's feature is by my compiler. And can you tell me about 'rewind a terminal''s meaning? why `stdin` is non-seekable? – lev.1 code Feb 19 '20 at 13:39
  • 1
    Debugging characters like newlines and blanks is difficult using `%c`. In context, using `%d` would be a good idea, though you’d probably need a newline at the end of each format string. However, that would, of course, change the output. – Jonathan Leffler Feb 19 '20 at 13:45
  • 2
    The order of evaluation of function arguments is not defined. Also: `getc()` can be a macro, which may cause problems when called without an intervening sequence point(like here). – wildplasser Feb 19 '20 at 13:48
  • 1
    If the standard input is a terminal, trying to use `rewind(stdin)` fails, but there is no way for `rewind()` to tell you that because it returns nothing (`void`). That's why you had to type `s` and return twice. If the input was redirected from a file, the `rewind(stdin)` would succeed and the first two characters of the file would be read (in some indeterminate order) by the `getchar()` calls. (If there's no prior input, that'll be the same two characters as the `getc(stdin)` calls read.) – Jonathan Leffler Feb 19 '20 at 13:54
  • 3
    Incidentally, my comment about "if there's no prior input" is why we like to see an MCVE ([Minimal, Complete, Verifiable Example](https://stackoverflow.com/help/mcve)) (or MRE or whatever name SO now uses) or an SSCCE ([Short, Self-Contained, Correct Example](http://sscce.org/)). If you had `#include ` and `int main(void) {` and `return 0; }` around the code you show, I wouldn't have needed to make caveats about "if there's no prior input". – Jonathan Leffler Feb 19 '20 at 13:57

1 Answers1

3

Explaining the output

The arguments to a function can be evaluated in any order that the compiler chooses.

The original code looked like this (missing printf("\n"); compared to the revised code):

printf("%c %c", getc(stdin), getc(stdin));
rewind(stdin);
printf("%c %c", getchar(), getchar());

Your 'output' in the question is extremely difficult to read and interpret. However, I'm reasonably sure that what you show consists of both your input and the program's output. You say you type s enter twice. The output you get, copied from the question, is:

s

 s
s

 s

The first s and the newline that follows are what you typed. The blank line is there because the newline was read by the getc(stdin) that appears first in the argument list, then there's the blank from the format string, and the s which was read by the getc(stdin) that appears second in the argument list. Your compiler, it seems, evaluates the arguments right to left — but you should not rely on that.

Now we run into a problem; there's a newline after the second s that isn't accounted for by the code you showed when I wrote this answer. Since then, you've added the printf("\n"); which explains the output you saw.

Putting that aside, the third s is followed by a newline and is what you typed. And again, the blank line is from the newline and then space and s is the rest of the output, and there's probably a newline after that too.

JFTR, when I run your code (program getc73 compiled from getc73.c), I see:

$ getc73
s

 ss

 s$
$

The $ is my prompt; the first s is what I type; then the newline, blank and s are printed (with no newline at the end); then the second s on the third line is my next s and return, followed by the newline and blank s. Since there's no newline at the end, my prompt appears immediately after the fourth s. I hit return to print another prompt at the start of the line.

Why rewind(stdin) is a no-op

You can't successfully execute an fseek() call on a terminal device. They don't have any storage so you can't move around like that. The rewind() function is effectively:

void rewind(FILE *fp)
{
    fseek(fp, 0, SEEK_SET);
}

So, the rewind(stdin) call fails to rewind, but it has no way to report that to you because it returns no value. It does no good; it doesn't do any harm either. If it had worked, you would not have needed to enter the information twice.

If the standard input had been a file instead of a terminal, then the rewind would succeed — disks are seekable.

Improved formatting

If you were on a POSIX system, I'd suggest you used a feature of POSIX printf() that allows you to print the same argument more than once:

#include <stdio.h>

int main(void)
{
    printf("[%1$c] %1$d [%2$c] %2$d\n", getc(stdin), getc(stdin));
    printf("[%1$c] %1$d [%2$c] %2$d\n", getchar(), getchar());
    return 0;
}

When I run that on my terminal (it's a Mac running macOS Mojave 10.14.6 using GCC 9.2.0), I get:

s
[
] 10 [s] 115
s
[
] 10 [s] 115

The lines with just the s on them are what I typed; the other lines show that the newline is printed before the letter s.

This probably won't work for you in VS2017 — the manual for printf() formats on Windows strongly suggests it won't work. Your best bet, probably, is to use:

#include <stdio.h>

int main(void)
{
    printf("%d %d\n", getc(stdin), getc(stdin));
    printf("%d %d\n", getchar(), getchar());
    return 0;
}

That generates:

s
10 115
s
10 115

Choosing a good format to show what you're seeing is an art form. Don't forget to include newlines at the end of (most) printf() format strings. Obviously, if you're building up a line of output piecemeal, you don't want newlines at the end of intermediate formats, but the majority of printf() statements should have a newline at the end of the format.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • " I suspect that means you didn't show \n at the end of the format string." You are right. Sorry for my carelessness... I edited my post and your answer gave me good grasp. – lev.1 code Feb 20 '20 at 02:18
  • note, you can `ungetc` on stdin (rewind by 1 character) – M.M Feb 20 '20 at 03:58
  • You're right, @M.M, but that isn't a seek operation — the character you pass to `ungetc()` needn't be one you read (with `getc()` or any other input primitive), though the most common usage is indeed to do that. The standard guarantees at least one character of pushback — some systems allow much more. See [`ungetc()` — number of bytes pushback](https://stackoverflow.com/a/7816922/15168) for my analysis 9 years or so ago: HP-UX and AIX, 1 byte; Solaris, 4 bytes; Linux and macOS, at least 4096 bytes (my program stops testing there). – Jonathan Leffler Feb 20 '20 at 04:51
  • See also C11 [§7.21.7.10 The `ungetc` function](http://port70.net/~nsz/c/c11/n1570.html#7.21.7.10) and also its ['future direction'](http://port70.net/~nsz/c/c11/n1570.html#7.31.11p2). – Jonathan Leffler Feb 20 '20 at 04:51
  • Can `fseek()` (and thus `rewind()`) be reliably assumed a no-op on non-seekable streams? POSIX suggests: [The behavior of `fseek()` on devices which are incapable of seeking is implementation-defined](https://pubs.opengroup.org/onlinepubs/9699919799/functions/fseek.html). – ad absurdum Feb 23 '20 at 06:04
  • @exnihilo — Effectively, yes. There's a moderate to good chance that `errno` is set by `fseek()`; I'd need to think hard about whether `rewind()` might modify it too. Otherwise, nothing happens — in effect. No harm is done; nothing breaks unless it was relying on being able to get the same data twice. There isn't a way to detect whether the stream is seek able other than trying it, AFAIK. – Jonathan Leffler Feb 23 '20 at 06:08
  • Under _ERRORS_ in the POSIX docs, it does say that `fseek()` may fail if "_a request was made of a nonexistent device, or the request was outside the capabilities of the device._" In that case `errno` is set to `ENXIO`. A seek request made to a non-seekable device seems like it would be "outside the capabilities of the device." The no-op outcome appeals to my horse-sense; POSIX also says that any call to `fseek()` may be followed by an I/O operation. Note that the C Standard merely says that a _successful_ call to `fseek()` may be followed by an I/O operation. – ad absurdum Feb 23 '20 at 06:33
  • I believe that the ”implementation defined” escape hatch is there just in case some POSIX implementation has particular problems. It would be a perverse implementation that did something other than an almost ontop, but you'd be able to find out what it does and when from the docs. – Jonathan Leffler Feb 23 '20 at 06:45