C++ Part I · Module 2 · Building and Running C++ Programs
Printing Output While You Learn
You have been using std::cout since Chapter 2.2 on the understanding that the details would come later. This is later — but only part of it, and the chapter is deliberate about which part. C++ streams are a substantial subject with their own module ahead of them, covering formatting, reading input, files and in-memory text. None of that is here. What is here is the amount of output you need to see what your programs are doing for the next twenty-six modules, because the ability to put a value on the screen is what turns a lesson from something you read into something you can test. Three things are worth more than the syntax: there is a second output stream meant for error messages and it exists for a reason you can demonstrate in one command, printing a bool does not give you what you expect, and the way you end a line decides whether your output survives a program that dies.
Foundation15 min readC++Part IFundamentalsOutput
Part I · Module 2 · Chapter 2.8 · Printing Output While You Learn
1. What This Chapter Is For
This is a tool chapter, and it is narrow on purpose.
Output in C++ runs through streams, and streams are a real subject: what a stream is, how to control the shape of what you print, how to read input, how to handle a read that fails, how to work with files. Module 23 covers all of it.
What you need before then is smaller and more urgent. Every module from Chapter 3 onward will ask you to predict what a program does and then check. Checking means seeing a value. So this chapter teaches the amount of output that makes the rest of the curriculum testable, and stops.
That is not a shortcut — it is sequencing. Printing well requires knowing about types and formatting that you have not met. Printing usefully requires almost nothing, and you need it now.
2. The Basic Form
std::cout << "value = " << x << '\n';Chapter 2.2 accounted for every piece of this: std::cout is the standard output stream, << sends the thing on its right to the stream on its left, and '\n' is a single newline character.
Two properties make it comfortable to use.
It chains. Each << hands the stream back, so the next one can use it. One statement can send any number of things, and they appear in the order written.
It adapts to the type. You never say what kind of thing you are printing. Text, a whole number, a character — each is handled correctly because the compiler selects the right behaviour from the type of the value. (That selection is operator overloading, and Module 15 is where it is explained.)
3. Printing Several Things
A complete program printing a mix of values and an expression:
#include <iostream>
int main()
{
const int expected_value = 42;
const int actual_value = 40;
const int error_count = 2;
std::cout << "expected " << expected_value
<< ", actual " << actual_value
<< ", diff " << expected_value - actual_value
<< ", errors " << error_count
<< ", clean " << (error_count == 0) << '\n';
return 0;
}Expected output:
expected 42, actual 40, diff 2, errors 2, clean 0Three things to notice.
One statement can span several lines. The statement ends at the semicolon, not at a line break — Chapter 2.1 §4. Lining up the << operators like that is a common and readable style.
You can print an expression. expected_value - actual_value is computed first and the result is sent. Anything that produces a value can go on the right of a <<.
clean 0 is not what you may have expected. (error_count == 0) is false, and a false value prints as 0. A true one prints as 1.
4. The Second Stream
There are two output streams, not one:
std::cout— normal output. The results.std::cerr— error output. Problems, warnings, diagnostics.
Both go to the terminal by default, so a beginner can be forgiven for thinking the distinction is decorative. It is not, and one command shows why.
#include <iostream>
int main()
{
const int error_count = 2;
std::cout << "checking registers\n";
std::cerr << "WARNING: " << error_count << " mismatches\n";
std::cout << "done\n";
return 0;
}Run it plainly and you see all three lines. Now separate them:
$ ./out 2>/dev/null ← discard the error stream
checking registers
done
$ ./out 1>/dev/null ← discard the normal stream
WARNING: 2 mismatchesThe two streams are genuinely separate channels, and whoever runs your program can route them independently. That is the whole point:
Results go to
std::cout. Anything that is not a result goes tostd::cerr.
This matters more in this field than in most. A tool whose output is consumed by another program — a regression script, a log parser, a comparison step — needs its results to be clean. Progress messages and warnings mixed into that output corrupt it. Send them to std::cerr and the consumer gets only the data, while an engineer watching the terminal still sees everything.
The ordering question
A reasonable worry: if the two streams are separate, do the lines come out in the wrong order when they are combined?
Measured. This program numbers its lines and alternates between the two streams:
#include <iostream>
int main()
{
std::cout << "1 checking registers\n";
std::cerr << "2 MISMATCH at 0x4000\n";
std::cout << "3 continuing\n";
std::cerr << "4 MISMATCH at 0x4004\n";
std::cout << "5 done\n";
return 0;
}Straight to the terminal, and with both streams redirected into one file:
$ ./order28
1 checking registers
2 MISMATCH at 0x4000
3 continuing
4 MISMATCH at 0x4004
5 done
$ ./order28 > combined.txt 2>&1 && cat combined.txt
1 checking registers
2 MISMATCH at 0x4000
3 continuing
4 MISMATCH at 0x4004
5 doneOrder preserved in both cases. The reason is a real property of std::cerr rather than luck: std::cerr is unbuffered. Anything written to it is sent immediately rather than being collected and written out later in a batch.
That is also why error messages are reliable. An error message that sat in a buffer while the program crashed would be lost precisely when you needed it — which is the subject of the next section.
5. '\n' and std::endl, Measured
Both end a line. std::endl does one more thing: it flushes the stream — forces everything collected so far to be written out now.
The usual advice is "std::endl is slower, so use '\n'." That is true and it is not the useful part. Here is the useful part, measured.
These two programs are identical except for how the line ends. Each writes a line and is then killed immediately, before the normal end-of-program shutdown that would flush anything pending:
#include <iostream>
#include <csignal>
int main()
{
std::cout << "line written with backslash-n\n";
std::raise(SIGKILL); // die before normal shutdown flushes
return 0;
}#include <iostream>
#include <csignal>
int main()
{
std::cout << "line written with std::endl" << std::endl;
std::raise(SIGKILL); // die before normal shutdown flushes
return 0;
}Run each with its output going to a file rather than the terminal, then look at the files:
$ ./flush_n > n.txt ; cat n.txt
← empty. The line is gone.
$ ./flush_endl > endl.txt ; cat endl.txt
line written with std::endlThe first file is empty. The program wrote the line, the line went into a buffer, the program died, and the buffer was never written.
The second kept it, because std::endl flushed immediately.
6. Using Output to Find Out What Is True
The practical reason this chapter exists. Chapter 2.2 introduced the loop: edit, build, run, compare with a prediction. Printing is how you compare.
Two habits make the difference between output that helps and output that adds noise.
Label every value. std::cout << x << '\n'; prints a number with no context, and three of those in a row are unreadable. std::cout << "transfers = " << transfers << '\n'; is self-explanatory in a log you read a week later.
Print what you believe, not just what you have. If you think a count is 8 at a certain point, print it there. The value of the output is in confirming or breaking a specific belief, and a belief you have not stated cannot be broken.
std::cout << "before: transfers = " << transfers << '\n';
// ... the part you suspect ...
std::cout << "after: transfers = " << transfers << '\n';This is deliberately primitive, and it is also what a large number of working engineers reach for first, because it needs no setup and works everywhere — including inside a simulator where a debugger may not be available. Module 25 teaches debuggers and the tools that find whole classes of bug automatically. Printing remains useful alongside them.
One warning worth having early: print statements added during debugging have a habit of being left behind. A program that prints its internal state on every transaction is slow and unreadable, and that output eventually ends up in a log somebody else has to parse. Remove them, or put them behind the conditional compilation from Chapter 2.5 §6 so a build can switch them off.
7. Mistakes Worth Making Once
Printing values with no labels. Fast to write, useless in a log. The reader a week later includes you.
Sending progress messages to std::cout in a tool whose output is parsed. The consumer gets your chatter mixed into its data. That is what std::cerr is for.
Expecting true and false from a bool. You get 1 and 0 until you ask otherwise, and asking is Chapter 23.2.
Assuming output appears when the statement runs. It appears when the buffer is flushed. To a terminal that is usually immediate; to a file or a pipe it is not. This is the single most confusing output behaviour for beginners and §5 is the demonstration.
Reading "endl is slow" as "never use endl". It flushes, and flushing is sometimes exactly what you want. The slowness only matters when it happens in a tight loop.
Leaving debug output in. It slows the program, clutters the log, and outlives the bug it was added for.
8. What You Should Be Able to Say Now
- This chapter is deliberately narrow: enough output to make the rest of the curriculum testable. Module 23 teaches streams properly.
<<chains, and adapts to the type of each value, so one statement can print text, numbers and expressions together.- A statement can span several lines; it ends at the semicolon.
- A
boolprints as1or0by default.std::boolalphachanges that and belongs to Chapter 23.2. - There are two output streams:
std::coutfor results,std::cerrfor anything that is not a result. - They are separate channels —
2>/dev/nulland1>/dev/nulldiscard one each — which is why a tool whose output is parsed must keep its results clean. std::cerris unbuffered, so its output is written immediately and survives a crash.std::endlends a line and flushes.'\n'only ends the line.- Measured: a program killed after writing with
'\n'to a file left the file empty; the same program usingstd::endlkept the line. - Buffering depends on the destination — a terminal is usually flushed per line, a file or pipe is not. The program did not change; the destination did.
- Use
'\n'by default,std::endlwhen a line must survive, andstd::cerrfor errors. - Label every value you print, and print the belief you are testing.
9. What Is Deliberately Left for Module 23
So you know what you do not yet have, rather than assuming you have it all:
| Not here | Chapter |
|---|---|
| What a stream actually is | 23.1 |
Width, precision, fill, hexadecimal, std::boolalpha | 23.2 |
| Reading input, and what happens when a read fails | 23.3 |
| Reading and writing files | 23.4 |
| Building and parsing text in memory | 23.5 |
Printing your own types with << | 15.5 |
The one you will want soonest is hexadecimal, as soon as Module 3 starts dealing with register values and bit patterns. It is three lines away and it is worth waiting for the chapter that explains the rest of the formatting system with it.
10. Exercises
Exercise 1 — predict the output. Write down what this prints, including the last value, before running it.
const int a = 7, b = 2;
std::cout << a << " / " << b << " = " << a / b << ", equal? " << (a == b) << '\n';Exercise 2 — choose the stream. For each line of a register-checking tool, say whether it belongs on std::cout or std::cerr, and why: (a) addr=0x4000 expected=0x1F actual=0x1F; (b) could not open vectors.txt; (c) checked 4096 registers; (d) MISMATCH at 0x4004.
Exercise 3 — explain the empty file. A colleague's program prints progress messages and crashes after an hour. The log file contains nothing from the last several minutes, though the terminal showed those lines during an earlier short test run. Explain both observations with one mechanism, and give two different fixes.
Exercise 4 — defend endl. Someone states the rule "never use std::endl". Give the case where it is the right choice, and say what makes that case different.
Exercise 5 — fix the output. Rewrite this so it would be useful in a log read a week later:
std::cout << transfers << '\n';
std::cout << cycles << '\n';Exercise 6 — the parsed tool. A script runs your tool and reads its output to extract a cycle count. You add a line printing starting... to std::cout. Describe what breaks and why, and give the one-word fix.
Exercise 7 — state your belief. You suspect a count is wrong after a particular step. Write the two output statements you would add, and explain what each possible result would tell you. A good answer says what you learn when the values are the same, not only when they differ.
11. Summary
- A tool chapter, on purpose. Enough output to make every later module testable; Module 23 owns streams properly.
<<chains and adapts to each value's type, so one statement prints text, numbers and expressions together.- A
boolprints1or0until you ask otherwise — Chapter 23.2. - Two streams:
std::coutfor results,std::cerrfor everything else. They are separately redirectable, which is why a tool whose output is parsed must keep results clean. std::cerris unbuffered, so its output is immediate and survives a crash.std::endlflushes;'\n'does not. Measured: killed mid-run writing to a file,'\n'left the file empty andstd::endlkept the line.- Buffering depends on the destination, not on your program — which is why behaviour changes when you redirect output to a file.
- Label every value, and print the belief you are testing.
- Remove debug output, or put it behind conditional compilation.
Module 2 is finished. You can write a program, build it deliberately, split it across files, see what the preprocessor did to it, follow it through all four stages, place any error in the stage that produced it, and print values to check what you believe. That is a complete working loop, and it is the last thing you needed before the language itself.
Module 3 starts there. Every program so far has used int without a word of explanation about what an int actually is, how wide it is, what happens when a value does not fit, or why const int transfers = 8; and int transfers; are meaningfully different statements. Those questions are the foundation of everything in C++ that follows, and they begin with the most load-bearing idea in the language: an object is storage with a type.
Standards & specifications
- Governing standard
- ISO/IEC 14882 (the C++ language standard)(opens ISO/IEC (JTC 1/SC 22/WG 21) in a new tab)
Defines the C++ language and its standard library — the type system and templates, the object model and object lifetime, initialisation and overload resolution, the memory model and the containers, algorithms and utilities the library provides. Like C's standard it is a FAMILY of editions (C++98/03, C++11, C++14, C++17, C++20, C++23) and the editions are NOT interchangeable: a feature this curriculum relies on may be unavailable, differently specified or merely proposed in another edition, so each lesson names the edition its claim depends on. It does not define any foreign-function interface to a hardware description language, nor what a compiler actually emits, nor platform behaviour on real silicon — those are the implementation's and the device's contracts. Working drafts published by WG21 are freely available and are what most engineers read day to day; a draft is not the published standard.
This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.
Where this fits
Part of the C++ Programming curriculum.
