Skip to content
VLSI Mentor

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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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:

Azvya Education Pvt. Ltd.VLSI Mentor
mix28.cpp — complete program
#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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
expected 42, actual 40, diff 2, errors 2, clean 0

Three 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.

Azvya Education Pvt. Ltd.VLSI Mentor
out.cpp — complete program, writing to both streams
#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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ ./out 2>/dev/null          ← discard the error stream
checking registers
done

$ ./out 1>/dev/null          ← discard the normal stream
WARNING: 2 mismatches

The 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 to std::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:

Azvya Education Pvt. Ltd.VLSI Mentor
order28.cpp — alternating 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ ./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 done

Order 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:

Azvya Education Pvt. Ltd.VLSI Mentor
flush_n.cpp — ends the line with '\n'
#include <iostream>
#include <csignal>

int main()
{
    std::cout << "line written with backslash-n\n";
    std::raise(SIGKILL);              // die before normal shutdown flushes
    return 0;
}
Azvya Education Pvt. Ltd.VLSI Mentor
flush_endl.cpp — identical, but ends the line with std::endl
#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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ ./flush_n    > n.txt    ;  cat n.txt
                                        ← empty. The line is gone.

$ ./flush_endl > endl.txt ;  cat endl.txt
line written with std::endl

The 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Output that answers a question rather than adding noise
    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

  1. This chapter is deliberately narrow: enough output to make the rest of the curriculum testable. Module 23 teaches streams properly.
  2. << chains, and adapts to the type of each value, so one statement can print text, numbers and expressions together.
  3. A statement can span several lines; it ends at the semicolon.
  4. A bool prints as 1 or 0 by default. std::boolalpha changes that and belongs to Chapter 23.2.
  5. There are two output streams: std::cout for results, std::cerr for anything that is not a result.
  6. They are separate channels — 2>/dev/null and 1>/dev/null discard one each — which is why a tool whose output is parsed must keep its results clean.
  7. std::cerr is unbuffered, so its output is written immediately and survives a crash.
  8. std::endl ends a line and flushes. '\n' only ends the line.
  9. Measured: a program killed after writing with '\n' to a file left the file empty; the same program using std::endl kept the line.
  10. 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.
  11. Use '\n' by default, std::endl when a line must survive, and std::cerr for errors.
  12. 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 hereChapter
What a stream actually is23.1
Width, precision, fill, hexadecimal, std::boolalpha23.2
Reading input, and what happens when a read fails23.3
Reading and writing files23.4
Building and parsing text in memory23.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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    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 bool prints 1 or 0 until you ask otherwise — Chapter 23.2.
  • Two streams: std::cout for results, std::cerr for everything else. They are separately redirectable, which is why a tool whose output is parsed must keep results clean.
  • std::cerr is unbuffered, so its output is immediate and survives a crash.
  • std::endl flushes; '\n' does not. Measured: killed mid-run writing to a file, '\n' left the file empty and std::endl kept 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.