2

I wrote the following program.

void main()
{
   int   *piarrNumber1   = (int *) calloc(1, sizeof(int));
   int   iUserInput      = 0;

   scanf("%d", &iUserInput);
   piarrNumber1[(sizeof piarrNumber1 / sizeof(int)) - 1] = iUserInput;
   printf("\n%d\n", piarrNumber1[0]);
}

I input "3" followed by a TAB from the keyboard. Nothing happens. Then, I press Enter Key. I get "3" printed and the program ends.

If "TAB" [Horizanotal Tab] and "Enter" [Newline] are both whitespace characters, why is their behaviour different?

Pankaj Dwivedi
  • 379
  • 1
  • 6
  • 16

2 Answers2

5

Details are operating system specific (since standard C99 does not know about terminals).

I assume you are using Linux.

First, stdio(3) is buffering the standard input stream and most other FILE* streams. You might try to change that with setvbuf(3), but this affects output buffering only.

More importantly, when stdin (actually the file descriptor used by it, i.e. STDIN_FILENO which is generally the value of fileno(stdin)) is a terminal (see isatty(3) to test that), the linux kernel is usually line-buffering the terminal (so called cooked mode) - at least to handle the backspace key. You might change that by switching the tty to raw mode (as every editor like emacs or vim or nano would do). See this question. But you should reset the cooked mode before your program exits.

So in normal cases, two levels of buffering happen: in the kernel for the line discipline of the terminal, and in the libc for the buffering of stdin

Read the tty demystified page and the Text Terminal HowTo

In practice, if you want sophisticated terminal input, use some library like ncurses or readline (don't bother using just termios)

See also stty(1) & termios(3) & tty_ioctl(4); read about ANSI escape codes.

Notice that this line buferring at two levels (libc and kernel) is specific to ttys. When stdin is a pipe(7) (as in echo foo | yourprogram) or a file (as in yourprogram < yourinputfile.txt) things are different.

In short, ttys are hard to understand, because they mimic complex and arcane hardware devices of the 1950s-1970s era.

Community
  • 1
  • 1
Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
  • Thanks, sir! I can understand the Buffering theory now. Is LINE BUFFERING the default for stdio stream? – Pankaj Dwivedi Mar 15 '15 at 07:10
  • Not every *stdio* stream is line buffered, but I believe those for ttys are. Details give me headache (even if I understood almost all of them before). So if you want to have better terminal input, use `ncurses` or `readline` – Basile Starynkevitch Mar 15 '15 at 07:15
  • Actually stdio buffering is completely irrelevant for input, as it is always buffered (needs a place to put characters read), and `scanf` reads from that buffer. The issue here is terminal buffering, controlled by `tcsetattr`. – Chris Dodd Mar 15 '15 at 07:18
  • I am not sure it is. To use `stdin` with less buffering, you'll need both to set the tty to raw mode and to set `setvbuf` the `stdin`; but that would be silly (better use `ncurses`) – Basile Starynkevitch Mar 15 '15 at 07:21
  • @BasileStarynkevitch: `setvbuf` only controls output buffering -- it has no effect on stdin or input of any kind. – Chris Dodd Mar 15 '15 at 07:26
3

Most OSes buffer keyboard input so that they can process backspaces properly -- the OS keeps input in a buffer and only gives it to the program when Enter is hit.

Most OSes also provide ways to control this, but the way is different for different OSes. On POSIX systems, the tcsetattr command is used to control this terminal buffering, along with lots of other things. You can read the termios(3) manual page for lots of information about it. You get the behavior you want by setting non-canonical mode:

#include <termios.h>
#include <unistd.h>
    :
struct termios attr;
tcgetattr(0, &attr);
attr.c_lflag &= ~ICANON;
tcsetattr(0, TCSANOW, &attr);

which causes the OS to send every keystroke to your program immediately (except for a few special ones it intercepts, like ctrl-C), without waiting for Enter, and without processing backspaces either.

Note that terminal settings are persistent across programs that use the same terminal, so you probably want to save the original settings as of when your program started and restore them before it exits.

Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
  • You might add the the terminal setting is somehow permanent (till changed by `termios` functions, or `stty` or `reset` commands). So a program doing that should ensure it is resetting the terminal to a sane state before exiting. – Basile Starynkevitch Mar 15 '15 at 07:41
  • Thanks, Chris! Will it be safe to assume that when accepting keyboard inputs for a C program run from a terminal in Unix and a Command line window in Windows, the default behaviour is to Line Buffer? – Pankaj Dwivedi Mar 15 '15 at 10:26
  • @PankajDwivedi: On startup/login/window creation the terminal settings will initially be canonical (buffering up to enter and processing backspaces). After that, they are whatever programs set them to. – Chris Dodd Mar 15 '15 at 18:14