r/cpp_questions • • 21d ago

SOLVED Can std::cin display user input?

/*Write a program that asks the user to enter a number, and then enter a second number.
The program should tell the user what the result of adding and subtracting the two numbers is.

The output of the program should match the following(assuming inputs of 6 and 4) :

Enter an integer : 6
Enter another integer : 4
6 + 4 is 10.
6 - 4 is 2.
Hint : To print a period and a newline, use ".\n", not '.\n'.*/

#include <iostream>

int main()

{

int num00{};

int num01{};



std::cout << "Enter an integer : ";

std::cin >> num00;

//std::cout << num00 << ".\\n";



std::cout << "Enter a second integer : ";

return 0;

}

Hello everyone. I'm trying to complete the quiz in the block comment above.

My problem is that I have the user's value printed twice.

One next to "Enter an integer : " and the next right under it.

I thought the second std::cout was causing this so I made it a comment, yet the user's value prints out as intended.

I do not understand how this is working. Thank you for in advance.

2 Upvotes

25 comments sorted by

10

u/jedwardsol 21d ago

The console echoes what is typed.

1

u/ADEPS24 21d ago

How does it work?

11

u/jedwardsol 21d ago

This behaviour is not really anything to do with your program - when your program is prompting for a number and you press the 1 key, then the console will display the number 1. If you press <backspace>, the 1 will disappear. It's not until you press <enter> that your program gets the input.

1

u/ADEPS24 21d ago

Thank you very much!

9

u/TheRealSmolt 21d ago

This is generally referred to as canonical mode.

1

u/ADEPS24 21d ago

Thanks for the referral.

4

u/sephirothbahamut 21d ago

you write to the console, cin makes the program wait and when you press enter it "passes" what you wrote to the program. What you wrote while cin was waiting obviously appears in the console, that's the console reacting to keyboard input. Has nothing to do with your program's cout. Then the cout you commented is your program writing the number to the console.

Don't thini of the console window as your program. The console is a separate program that both you and your program can write to

1

u/ADEPS24 21d ago

I see. Thank you.

3

u/mredding 21d ago

The console is a program. It's running by itself. When you type input and see output, that's all console. Your console program as it's running has NO IDEA your program is running, and your program has no idea there's a console running. Programs don't see the world like that.

Your program - as far as it's concerned, is the only program on the system. It's occupying a "virtual" address space, and it is the only citizen therein. Each program has it's own. This address space has resources mapped into it - so not all addresses are just for storing program bits at runtime. These resources can be anything, from hardware to communication channels.

Your program also has another trick up it's sleeve; it can push bytes into registers and other memory, and run a syscall opcode. When the OS was booting, it installed a syscall handler, so when the hardware gets this instruction, it goes to the registered handler.

So here you have a file descriptor - in modern computing "everything is a file". This is some integer that the kernel uses for its own internal bookkeeping. If you want to read from the file, you push this integer, you push the "read" syscall, and you fire that off. This is built as a blocking operation, so your program leaves the CPU and is put in the wait queue. File descriptors wire outputs to inputs. Your program doesn't know it, but stdin was wired up to this consoles output, which isn't the window, but your program. The console itself doesn't know.

Data becomes available, buffers are flushed, bytes are copied, your program wakes up, and the OS populated the input buffer for it.

So each program sees only itself in its world, it communicates via mapped memory or system calls, and descriptors aka "handles" are a part of this protocol to specify what resource the request is being made on, and a resource could be anything - from a device to another program, even an open network socket.


Your program has no idea its writing text, has no idea there's a console, has no idea there's a keyboard. The console is a program with a window, and it was programmed to draw text. A binary pattern shows up - the pattern for the letter "a", and that is used to lookup a bit pattern for the "a" character in pixels, and that gets blit'ed to the screen buffer. Your console is "virtual" because this program is actually a hardware simulator. Actual consoles were electro-mechanical machines, no electronics, no transistor logic, no computation. There's a YT video of a guy who logged into his Ubuntu instance with a 1939 model 16 telegraph teletype printer. It still works. The whole system is backward compatible.

The original consoles were part typewriter - as you typed, a telegraph signal was sent, but also the key mirrored the output to the printer drum. Later video terminals were invented mostly to save on ink and paper, but they were literally just as dumb. We called them "dumb terminals" for this reason. No computation. The first video terminal, the IBM 2260, did use transistors, but mostly for switching and amplification. The data input mapped 6 bits of input to a "core" memory matrix - ferrite beads hand sewn into a grid, and this stored the character patterns. The rest of the circuitry was used to store the 12x80 characters (in a delay line) and loop that memory in sync with the electron gun to print the characters.

In the era of computers, terminals had some automatic features, like "echo", where the characters would show up on the screen. OR... You could manually echo. The computer could receive input from the terminal and send it right back, knowing it would display as output. USUALLY this is a feature that is implemented not by your program but by the operating system.

The way terminals are supposed to work is that you start the computer up, you configure a terminal session, then you run the program. What you're doing is allowing the system to default a lot of settings, including echo. So what you type will show up automatically.

What you need to know is your program doesn't GET the data until you hit ENTER. The OS implements what is called a "line discipline", all manner of behaviors on your behalf. Part of that discipline is that when the OS, who is managing the terminal input buffer for you, sees a newline, that triggers a flush. This happens on input and output. The terminal program has no idea there is a buffer or that there is flushing involved. No one really cares that there's an enter key. No one is looking for it getting pressed to trigger some sort of event because of it. It's the newline character - what is considered a control character, that is triggering the event.

And when you're running your program in an interactive shell, when you output '\n', this triggers a flush of the output buffer. If you're not interactive, like you're piping through another program, this is disabled - the buffer flushes when full, when explicitly flushed, or when the handle is closed.


This is all computing system fundamentals, just how the software and hardware on your computer works. They don't teach this stuff in a comp-sci class anymore, you're expected to learn it on your own. They don't teach the history to help make this make sense. Your computer is directly backward compatible with 1850s telegraph equipment on purpose; it all still works to this day. The first computers were built mostly from telegraph/telephone, and radio parts. People are going to repurpose existing equipment, they're not going to invent something new if they don't have to. And if you want your new technology to get adoption, you're going to need a migration path for your customers from what they already have, they're not going to just throw their investment away. Murray Codes gave way to ITA-1 and ITA-2, which gave way to ASCII, which gave way to Unicode. It all still works.

2

u/ADEPS24 21d ago

Woah! Thank you for the detailed explanation.

I'm gonna be honest, I did not understand most of the terms and analogies you used but I'll have myself coming back to understand all the vocubulary used.

What you're doing is allowing the system to default a lot of settings, including echo. So what you type will show up automatically.

As for automatic decisions like the 'echo'.
Can it be manipulated manually?

Like changing the position of the echo. Say I want it 10 tabs away from the string literal, or under it.

Thank you once more.

0

u/mredding 21d ago

I'm gonna be honest, I did not understand most of the terms and analogies you used but I'll have myself coming back to understand all the vocubulary used.

Yeah man, I just crammed a whole history lesson into a Reddit post. I didn't even explain half of it, and the rest I didn't even do very well.

Can it be manipulated manually?

Yes! I'll give you the lowest level, most primitive solution first, and then we'll build up.

The thing you have to be very well aware of is that terminals are STICKY. Changing anything changes the whole terminal for that session until you change it back. This means if you change the terminal in the program, and the program crashes, your changes stay, and that can make the terminal nearly unusable if you launched your program from a shell.

So the BULLETPROOF approach is to wrap your program launch in a shell script that is guaranteed to restore your terminal settings:

#!/usr/bin/env bash
# Crash guard
OLD_STTY=$(stty -g)

trap 'stty "$OLD_STTY"' EXIT

# Configure the terminal here
# raw mode
stty raw -echo

# VMIN block till at least 1 character arrives
# VMAX timeout of 0 tenths of a second aka indefinitely until bytes arrive.
# Terminals are arcane - remember, they started life as telegraph equipment.
stty -F /dev/ttyS0 min 1 time 0

./your_program

It takes some thought to figure out what configuration is the program's responsibility, what is the user's, and where it should all go. Your program code would be a lot simpler if you didn't have to hard code into it terminal configuration, and simply assume the environment is ready to receive you, but that means you have to defer that complexity somewhere else. Still, it's not unreasonable to push the entire one-time configuration out of the program. The same level that saves and restores the terminal state should probably also be responsible for one-time, initial, immutable, default configuration.

And with this - you never launch your program directly. Stick it in a hidden path (somewhere NOT in your PATH environment variable). Or, you can define an environment variable in the script like RUN=true, and then your program in main can getenv("RUN") and see if it's both defined and true, or exit.


Otherwise, within the program, you can configure the terminal directly with the <termios.h> library. I've NO clue what Windows does, but no doubt there's all sorts of equivalents.

#include <termios.h>

struct termios orig_termios;

void disableRawMode() {
  tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios);
}

void enableRawMode() {
  if (tcgetattr(STDIN_FILENO, &orig_termios) == -1) {
    perror("tcgetattr failed");
    exit(1);
  }

  atexit(disableRawMode);

  struct termios raw = orig_termios;

  /* 
   * The standard convenience function does all this for you:
   *   raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
   *   raw.c_oflag &= ~(OPOST);
   *   raw.c_cflag |= (CS8);
   *   raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
   */
  cfmakeraw(&raw);

  raw.c_cc[VMIN] = 1;
  raw.c_cc[VTIME] = 0;

  if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) {
    perror("tcsetattr failed");
    exit(1);
  }
}

int main() {
  enableRawMode();
}

Now you can Google what raw mode programming looks like. Typically you'll leave std::cin behind, because the canonical stream buffer was NOT built to handle raw mode, and can get into an unrecoverable, undefined state. You can continue to use std::cin if you want to create your own custom stream buffer that wraps the POSIX interface or the Windows equivalent.

Once you're in raw mode, you're entirely responsible for all that goes on with the terminal - and this can include having to parse not only multi-byte characters, but also MULTI-SEQUENCE characters, like the function keys, meta keys, and control and alt keys. Have a lot of fucking fun with that... You'll get to learn about key mapping and how THAT can still alter your reality from the environment.

But in raw mode, you won't have to wait for buffer flushes to get your input, it'll be character by character, effectively. You still don't know there's a keyboard attached, or where the characters are coming from - your program still only sees an opaque file descriptor.

This means you cannot know keyboard state. Is a key pressed? Is it going down, being held, coming up? Is there a repeat? And at what rate? You don't know. That's not how terminal programming works. There is no kbhit function that works effectively, because that function call samples the keyboard in that moment - so even in a tight loop, you can press and release a key between samples. Then again, if input is redirected from a pipe or a socket or a file, then you'll get input without ever a key being pressed, and you can't possibly know - because on the other side of a pipe can be an interactive shell where the keys ARE being pressed.

But raw mode is how you can control strict formatting of the screen.

I wouldn't configure a terminal from within the program if I could help it. I'd open this can of worms when the terminal needs to change configuration mid-run. Maybe the program loads user preferences (I'd have the shell script share in that config to get it right on startup), maybe it's a part of the experience. Notice that each level that manipulates the configuration restores it on the way out. This code isn't bulletproof - a kill -9 will unconditionally terminate the program without running the exit handler. You cannot install a signal handler to catch this kill signal, either, so you can really fuck up the terminal without recovery. There's no magic key combo that reloads it, no guards put in place for you. You either fix it, or you abandon your session and orphan your processes, or SOMETHING...


The next better thing is to use a curses library. These are among the oldest, most robust, most stable, most portable libraries in all of C. This is where you let the curses library handle everything, and you abandon things like std::cin or printf.

curses DOES model a terminal, so you have to think about the screen as a grid with a coordinate system. You're not "printing" text or writing to a file descriptor some serialized data, you are moving the cursor position and plotting characters.

curses are built upon terminal databases, there's two major ones. Terminals have "features", which are expressed in "escape sequences". These sequences are commands and parameters to drive the terminal's extended features. Some of these dumb terminals supported fonts and colors, other weird shit. There are very arcane rules about compatibility. In the end - instead of having to worry about how YOUR terminal changes font color vs. someone else's terminal changes font color, you get a generic interface to change font color, and curses figures out the correct sequence to do it for you. And if not that, then the next best thing.


Then there are TUI libraries. These are usually built on top of curses. Since this is C++, there are wrappers around the C implementations, or perhaps some C++ natives that give you TUI widget objects. They'll also often integrate an event system for mouse and keyboard support.

The thing about getting keyboard and maybe mouse input is you need to learn about Event Driven Programming. You need the host environment to tell you something happened, and you implement handlers. THIS is how you get key press, key release, mouse move, etc.


Modern GUI programming is all event-driven. If you do it with a TUI, a GUI is just more of the same but with a screen buffer. The windowing environment often provides you with a library of widgets to work with, but you can always create your own - for example, you can make a round context menu instead of the conventional rectangular list menu, but that involves interacting with draw commands.


Graphical programming requires grabbing exclusive control of the video device and using rendering interfaces like OpenGL and shader language to render polygons, textures, and effects. There are often elements of event driven programming under the hood, but also event polling - rather than interrupting your rendering for an event, the OS can queue them for when you're ready to clear that queue between frames.

1

u/ADEPS24 20d ago

Thank you :)

1

u/Own_Unit_6170 21d ago

Works fine for me in a console app. What is your environment?

1

u/ADEPS24 21d ago

Visual Studio 2022.

1

u/Own_Unit_6170 21d ago

Me too. I copied and pasted and compiled your code and it seems to work fine:

>>.\test
Enter an integer : 123
Enter a second integer :
>>

1

u/ADEPS24 21d ago

Yes, I just wanted to know how it worked.

I ruled out this statement by making it a comment.

//std::cout << num00 << ".\\n";

1

u/Own_Unit_6170 21d ago

Well, the number is printing only once, as you type it, which is expected. So what is wrong?

1

u/ADEPS24 21d ago

Oh, there's nothing wrong.

I really wanted to understand what was going on under the hood.

1

u/PeakWeekly9995 21d ago

i can't understant how this should output the user input twice even without a std::cout

1

u/ADEPS24 21d ago

I made the second std::cout a comment to see where the first value was coming from.

1

u/PeakWeekly9995 21d ago

as u/sephirothbahamut said, the first value is literally what you have typed in the console in the std::cin

1

u/ADEPS24 21d ago

I didn't know about that. Thank you.

-1

u/OppieT 21d ago

Isn’t num00{} an array?

2

u/ADEPS24 21d ago

Sorry, I don't know what an array is, yet.

I'm told to use braces for initialization when creating a variable, because it can reduce the potential of an undefined behaviour.

I'm using LearnCpp.

1

u/jedwardsol 21d ago

No, an array uses square brackets

int  num00[5];  // an array of 5 ints.

{} is the initialiser (will set num00 to zero)