2

I have a confusion related to using puts(), gets(), putchar() and getchar() simultaneously use in the code.

When I have run the below code, it is doing all steps: taking the input, printing the output, again taking the input, printing the output.

#include <stdio.h>

int main() {
    char ch[34];
    gets(ch);
    puts(ch);

    char g;
    g = getchar();
    putchar(g);
}

Output:

Priyanka
Priyanka
J
J

But, when I am using this code: It is only doing two steps: taking the input, printing the input, then one line space. I am not getting why it behaves like this.

Code:

#include <stdio.h>

int main() {
    char g;
    g = getchar();
    putchar(g);   
 
    char ch[34];
    gets(ch);
    puts(ch);
    getch();
}

Output:

P
P
chqrlie
  • 131,814
  • 10
  • 121
  • 189
Priyanka
  • 21
  • 4
  • 1
    `gets()` is deprecated from both C and C++ standards. It's very dangerous to use too. – Rohan Bari Feb 23 '21 at 06:01
  • 1
    See [Why `gets()` is too dangerous to be used](https://stackoverflow.com/questions/1694036/why-is-the-gets-function-dangerous-why-should-it-not-be-used) for a detailed discussion of why you should never, ever use `gets()` and about alternatives. – Jonathan Leffler Feb 24 '21 at 15:32
  • Note that `getch()` used in the last example is not a standard C function. – Jonathan Leffler Feb 24 '21 at 15:35
  • The sequence of operations in the second example is: you type "P" and hit return (and the letter and newline displayed by the terminal driver); the `getchar()` returns the letter 'P'; the `putchar()` outputs the 'P', but it doesn't appear yet; the `gets()` reads the newline and returns an empty string; the `puts()` outputs the empty string and a newline, also forcing the 'P' to be displayed. If you typed "Priyanka" instead of just "P", you'd get a similar result with "Priyanka" appearing twice. If you used `putchar('X')` after `putchar(g);` and typed "Priyanka", you'd see "PXriyanka". – Jonathan Leffler Feb 24 '21 at 17:16
  • Note that production code would need to check that the input operations succeeded, and you'd also need to use `int g` instead of `char g` because `getchar()` returns an `int`, not a `char` (and the difference does matter in some circumstances). – Jonathan Leffler Feb 24 '21 at 17:18

3 Answers3

2

There are some problems in the code and the input mechanisms are more complex than you infer:

  • you should not read input with gets(): this function cannot be used safely because it does not receive information about the destination array size so any sufficiently long input line will cause a buffer overflow. It has been removed from the C Standard. You should use fgets() instead and deal with the newline at the end of the buffer.
  • g should have type int to accommodate for all the values returned by getc(), namely all values of type unsigned char (in most current systems 0 to 255) and the special negative value EOF (usually -1).

Here is a modified version:

#include <stdio.h>

int main() {
    char ch[34];
    if (fgets(ch, sizeof ch, stdin))
        fputs(ch, stdout);

    int g = getchar();
    if (g != EOF)
        putchar(g);
    return 0;
}

Output:

Priyanka
Priyanka
J
J

Regarding the behavior of the console in response to your program's input requests, it is implementation defined but usually involves 2 layers of buffering:

  • the FILE stream package implements a buffering scheme where data is read from or written to the system in chunks. This buffering can be controlled with setvbuf(). 3 settings are available: no buffering (which is the default for stderr), line buffered (usually the default for stdin and stdout when attached to a character device) and fully buffered with a customisable chunk size (common sizes are 512 and 4096).
  • when you call getchar() or more generally getc(stream), if a byte is available in the stream's buffer, it is returned and the stream position is incremented, otherwise a request is made to the system to fill the buffer.
  • if the stream is attached to a file, filling the buffer performs a read system call or equivalent, which succeeds unless at the end of file or upon a read error.
  • if the stream is attached to a character device, such as a terminal or a virtual tty like a terminal window on the graphics display, another layer of buffering gets involved where the device driver reads input from the input device and handles some keys in a special way such as Backspace to erase the previous character, cursor movement keys to move inside the input line, Ctrl-D (unix) or Ctrl-Z (windows) to signal the end of file. This layer of buffering can be controlled via the tcsetattr() system call or other system specific APIs. Interactive applications such as text editors typically disable this and retrieve raw input directly from the input device.
  • the keys typed by the user are handled by the terminal to form an input line, send back to the C stream API when the user types Enter (which is translated as a system specific end of line sequence), the stream functions perform another set of transformations (ie: converting CR/LF to '\n' on legacy systems) and the line of bytes is stored in the stream buffer. When getc() finally gets a chance to return the first available byte, the full line has already been typed and entered by the user and is pending in the stream or the device buffers.

In both programs, getchar() does not return the next byte read from stdin until a full line has been read from the terminal and stored in the stream buffer. In the first program, the rest of this line is ignored as the program exits, but in the second program, the rest of this line is available for the subsequent gets() to read. If you typed J and Enter, the line read is J\n and getchar() returns the 'J', leaving the newline [ending in the input stream, then gets() will read the newline and return an empty line.

chqrlie
  • 131,814
  • 10
  • 121
  • 189
0

Between the lines of the statements putchar() and gets() that I don't recommend using, discarding the input stream until EOF or till a newline occurs solves the problem:

.
.
int c;
while ((c = getchar()) != EOF && c != '\n')
  ;
.
.

I would recommend using fgets(3) which is quite safer to use, for example:

char str[1024];
if (fgets(str, sizeof str, stdin) == NULL) {
  // Some problem, handle the error...
}

// or, Input is okay...
Rohan Bari
  • 7,482
  • 3
  • 14
  • 34
0

Well, you have a problem here. You use a function in your second sample code that is not part of the stdio package.

You call getch() which is not a stdio function. It is part of the ncurses library, and, if you don't specify on compilation that you will use it, then you cannot get an executable program. So this make me thing you are not telling all the truth.

Just taking the function getch() of of the program you get the full line

Priyanka

output, and the program terminated. I guess you used getch() to stop the output until you press a character. But as curses library requires you to call initscr() before calling any other curses library function, it is not correctly initialized, and the output you get can be wrong due to this.

I'll not repeat what others have already told you about the use of gets(), it is still in the standard library, and knowing what you do, you can still use it in properly controlled environments. Despite of that, the recommendation others have given to you is not applicable here, as you have not overflowed the short buffer you have used (of only 34 chars, too short, too easy to hang your program or to crash it)

The functions from stdio use a buffer, and the unix tty driver is also interferring here. Your terminal will not make available any character you input to the program until you press the <ENTER> key, then all those characters are read by the program into a buffer. They are consumed from the buffer, until it is empty, so it doesn't matter if you read them one by one (with fgetch(), or all at once (with fgets() ---i'll use this, more secure, function, from this point on) Everything just happens once you press the <ENTER> key.

fgetch() only takes one character, so if more than one are available, only one character is taken from the buffer, and the rest wait their turn. But fgets() reads all (and fills the buffer) until a \n is read (this is why gets() is so dangerous, because it doesn't know the size of your buffer /it doesn't have a parameter indicating the size of the buffer, as fgets() has/ and cannot control the read to stop before overflowing it)

So, in your case, as you press a series of characters, then hit return, the first sample reads the full string, and then the second getchar() takes the first of the second line (but you need to input two complete lines at that point) The second sample read the first char when you called getchar(), and the rest of the line when you called gets().

To read one character at a time, without waiting for a full line to be input, the terminal driver has to be programmed to read characters in raw mode. Cookied mode (the default) is used by unix to read complete lines, this allows you to edit, erase characters on the line, and only input it when you are ready and hit the <ENTER> key.

If you are interested in reading chars one by one from the terminal, read the manual page termios(4) which explains the interface and iocontrols to the tty device. The curses library does the necessary housekeeping to put the terminal in raw mode to allow programs like vi(1) to read the input char by char, but you need then not to use stdio directly, as its buffering system will eat the characters you try to get to eat with curses.

Luis Colorado
  • 10,974
  • 1
  • 16
  • 31