TRT
Sign in

Foundations · Module 1 · Lesson 2 of 9

Anatomy of Your First Program

Read a six-line program token by token: who calls main, what std:: and the insertion operator really mean, how a chain of insertions works, and where the returned number goes.

FreeAbout 9 minutes

By the end you can

  • Name what each line of a minimal C++ program does, from the include to the closing brace
  • Explain why the std:: prefix exists and what a namespace prevents
  • Trace a chain of insertion operators and predict the text it writes
  • Predict the exit status a program reports, including when main has no return statement

Builds on From Source Text to a Running Program.

01 · A question first

Nobody in your code calls main, yet it runs and hands back a number. Who receives that number, and what do they do with it? And how can a single line mix text, arithmetic and a line break and still print a clean result?

Make a guess, then step through the trace below to check it.

02 · Watch it run

One run of a six-line programstep 1 of 7
#include <iostream> int main() {    std::cout << "Sum: " << 2 + 3 << "\n";    return 0;}
  1. part 0startup code(looked at in this step)
  2. part 1main
  3. part 2buffer: empty
  4. part 3status: none

Before any of our code runs, the program was started by startup code that the linker added. The four cells are the actors: that startup code, main, the output buffer behind std::cout, and the exit status that will be reported at the end.

Machine work: startup code: prepare std::cout and the other standard streams

03 · The idea

Start with the problem

Here is a program that does a tiny amount of work: it adds two numbers and prints the result.

cpp
#include <iostream>
 
int main() {
    std::cout << "Sum: " << 2 + 3 << "\n";
    return 0;
}

Six lines, and almost every token raises a question. Nobody in this file calls main, so what runs it? What is std:: doing in front of cout? How can one statement mix a piece of text, an addition and a line break, and print something sensible? And when the program says return 0, who is on the receiving end of that zero?

The last lesson followed a file through the build. This lesson reads the program itself, token by token, until none of those six lines is a mystery. Each answer connects to something you already know: the startup code the linker added, the header the preprocessor pasted in, and the symbol the linker had to resolve.

How the idea works

The trace above follows one run of this program. Its four cells are the startup code, main, the output buffer behind std::cout, and the exit status. Here is what each part of the source does.

The include line. #include <iostream> is a preprocessor directive, as in the last lesson. Angle brackets tell it to look in the compiler's standard header locations; quotation marks would make it search your own project first. The header declares std::cout and its relatives. Without the line, the compiler has never heard of the name and stops with an error.

The function main. A function is a named block of code. In int main(), int is the type of value the function returns, main is its name, and the empty parentheses say it takes no inputs. The body sits between the braces. main is special: it is the program's entry point, and your code never calls it. The startup code that the linker added calls it, as the second step of the trace shows. A program has exactly one main, and its return type must be int. A form that receives command-line arguments exists too; a later lesson covers it.

Statements. Each instruction in a function body is a statement, and most end with a semicolon. Statements run in order from top to bottom. Whitespace and line breaks mostly do not matter to the compiler; the semicolons do.

The std prefix. The standard library keeps all its names inside a namespace called std. The name std::cout means "the thing called cout that lives in std", and :: is the scope operator that says so. Namespaces prevent clashes. If you write your own function count, it does not collide with the library's std::count, because the two have different full names.

Streams and the insertion operator. std::cout is an output stream: an object that accepts characters and sends them to a destination, normally the terminal. The << symbol, here called the insertion operator, takes a stream on its left and a value on its right, converts the value to characters and appends them. The detail that matters most is what it gives back: the same stream. The trace shows this at its third step. Because each << returns std::cout, the next << has a stream to work on, and the whole statement groups like this:

cpp
((std::cout << "Sum: ") << (2 + 3)) << "\n";

Literals and arithmetic. "Sum: " is a string literal: five characters plus a terminating zero byte, stored in the executable's read-only data before the program starts. 2 + 3 is ordinary arithmetic, and because + binds more tightly than <<, it needs no parentheses. The CPU adds the numbers and the stream then turns the resulting 5 into the character 5.

Return and exit status. return 0; ends main and hands the number 0 to the startup code, which passes it to the operating system as the program's exit status. By convention 0 means success and any other value means failure. The number is never printed; it goes to whatever launched the program. A shell can read it:

bash
./sum
echo $?
text
Sum: 5
0

One more rule belongs to main alone: if execution reaches its closing brace without a return, the program behaves as if it had written return 0;.

Why it is correct

The chain works because of two rules that fit together. First, << associates left to right, so a << b << c means (a << b) << c. Second, the library defines the insertion for streams to return the stream it was given, as a reference to the very same object and not a copy. So every << in the chain writes into the same std::cout, in order. If any step returned something else, the chain would break at the next <<.

Order matters for more than output. Since C++17 the left operand of << is fully evaluated before the right one, which is why "Sum: " always appears before the 5 is computed. Before C++17 the order in which operands were evaluated was unspecified, and a line that called functions with side effects inside a chain could behave differently across compilers.

The same symbol does different jobs depending on the types around it. For two integers, 1 << 2 is a bit shift that gives 4. For a stream on the left, the library has supplied its own meaning through operator overloading: it defines a function named operator<< that takes a stream and a value, and the compiler picks the right one from the types. A later module covers overloading properly.

Finally, the exit status exists because a process needs a tiny, reliable channel to say whether it worked, without anyone parsing its text output. An integer is enough. That is why main returns int, and why the language lets you skip return 0;: the common case of success should cost no boilerplate.

What it costs

Arithmetic costs nanoseconds; output costs far more. Getting characters to a terminal or a file requires a system call, a request to the operating system kernel, and each one typically costs microseconds or more. Streams therefore keep a buffer, a block of memory where characters wait until there are enough of them, or until a flush is requested, to be sent together. In the trace, the text sits in the buffer after step five and reaches the screen only in the last step.

This is why the choice between a newline character and std::endl matters. A plain newline adds a character. std::endl adds the newline and also forces a flush. Printing 100,000 lines with std::endl to a file or a pipe is commonly several times slower than using plain newlines, sometimes by an order of magnitude, because each line triggers its own system call instead of one per few thousand characters. On an interactive terminal, line-by-line display is usually what you want anyway, so the difference shows up in bulk output.

C++ streams are also synchronized with the C library's printf family by default, so the two can be mixed safely. That synchronization costs speed. Programs that only use C++ streams can switch it off with std::ios::sync_with_stdio(false), a trick common in competitive programming. Call it before the first output, and do not mix printf and std::cout afterwards.

A using namespace std; line costs nothing at run time. Its cost is clarity: it pulls thousands of names into scope, and a call to count can become ambiguous. The exit status costs nothing either.

Pitfalls and variations

Compile errors from missing pieces. Leave out the include and GCC reports that cout is not a member of std. Leave out the std:: prefix and it says cout was not declared in this scope. Both come from the compiler stage, not the linker, because the compiler cannot check the call without a declaration.

The misplaced semicolon. A missing semicolon is usually reported on the line after the real mistake, because the compiler only notices when it meets a token that cannot continue the previous statement. Look one line up.

The void main trap. Some compilers accept void main(), but standard C++ requires int main. Use the standard form.

Shift versus insertion. std::cout << 1 << 2 prints 12, not a shifted number. To print a shift result, parenthesize it: std::cout << (1 << 2) prints 4. Related: << binds more tightly than the comparison operators, so std::cout << a == b groups as (std::cout << a) == b, which either fails to compile or compares the wrong things. Wrap comparisons in parentheses.

Treating the exit status as output. return 5; prints nothing; the number is invisible unless a shell, script or build tool reads it. On Linux and macOS only the low eight bits are kept, so return 256; reports 0.

Variations. std::cerr is a second output stream meant for error messages, kept separate so a user can redirect normal output to a file and still see the errors. std::exit(code) ends the program from any function, and the named constants EXIT_SUCCESS and EXIT_FAILURE from the header <cstdlib> are the portable way to spell the two common statuses. C++23 adds std::print and std::println in the header <print>, which format text without chains of <<, though compiler support depends on the version you have. The C function printf also works in C++, but it checks nothing about the types you pass.

Where it is used

Every C++ program has a main, and every command-line tool you use, from compilers to git to grep, reports an exit status. Build tools such as Make and CMake stop when a compile step returns a nonzero status, test runners and CI systems turn nonzero statuses into red builds, and shell scripts branch on them with if, && and ||. Output through streams is how nearly every console program talks to its user.

The program above prints 5 and forgets it. The next lesson gives values somewhere to live: variables, which are names for bytes in memory, so that the 5 can be kept, changed and used again.

04 · Prove it

Question 1 of 4 · answer from memory

Your program has one function named `main`. Which part of the program actually calls it?

05 · Practice

Something wrong with this lesson? Report a problem

Not started

Sign in to save your learning progress.

Sign in to save