Workflow with CPP
Sections
- Stages of your source code
- The graph
- Preprocessor
- Compiler
- Assembler
- Linker
- Compiler as a whole
- Practical Starting Point
- Windows
- Linux and Mac
- Mac
- Linux
- Writing your first program
- On Windows
- On Linux and Mac
- Where are the intermediate files?
- A note on C++ versions
- Footnote
- Preprocessor
- Assembler and Linker
- Putting it together
Stages of your source code§
The previous section teaches how to read C++ code, but doesn't teach you how to write one, compile it, link it, and so on. Don't worry if words like "compile" or "linking" sound unfamiliar right now — we will go through each of them step by step.
The graph§
Preprocessor§
The preprocessor eats .cpp files (and .hpp or .h files if they are #included) and outputs .i files, where the i stands for intermediate.
We have already seen the preprocessor in action without naming it, back when we introduced #include <print>. I stated that it replaces that line with the exact content of print.hpp. This is the job of the preprocessor. Now, you might think it is odd to have a whole separate machine just for replacing text — and you'd be right. The preprocessor actually does much more than that. It processes what we call macros, and #include is only one of them. We will introduce macros properly later on; for now, just know that all macros start with the hash symbol #, for example #pragma once or #define.
Compiler§
This is the only part where the term "C++" becomes truly relevant, and I will explain why in the footnote.
This is the part where C++ code (from the .i files) actually gets translated into code that is language-independent — meaning it no longer has anything to do with C++ specifically. That output is assembly code. Note that assembly is still architecture-dependent (x86 vs x64 vs ARM vs RISC-V, etc.), meaning it is tied to the specific CPU family you are targeting. Assembly is only one level up from literal machine language (the binary machine code that the CPU actually executes). This is the lowest abstraction level we can work with that is still human-readable.
This is also the only stage that can cause language errors — things like syntax errors (missing semicolons, unmatched braces) or grammar errors (using a keyword incorrectly). These are called compilation errors.
Assembler§
This one is much easier to explain. Now that we know what compiling means (translating one language into another), an assembler is simply a kind of compiler that translates assembly code (.s files) into machine-language binary code (.o files, where o stands for object). The .o files contain actual binary instructions that your CPU can understand, but they are not yet ready to run on their own.
Linker§
Since every .cpp file is compiled into its own .o file independently (if this wasn't obvious from the previous explanation, well, now you know), even though each .cpp file compiles fine on its own, that doesn't mean the resulting .o file can run successfully.
For example, main.cpp becomes main.o. It compiles fine because it has the print function's declaration (a promise that the function exists somewhere), but it does not have its definition (the actual body of the function). Thus, main.o by itself cannot be run successfully. What the linker does is take print.o — which contains the function's definition — and link it to main.o so that the main function knows where the print function's definition lives.
If the linker cannot find the definition for the print function, it will throw an error, which we call a linking error.
After the linker is done, the output is an executable. On Windows, this is typically a .exe file. On Unix-like systems such as Linux or macOS, the file extension is irrelevant — what makes a file executable is its permission bits (a property set by the operating system). By default, the compiler names the output a.out if you don't specify a name.
Compiler as a whole§
Even though we have seen that the word "compiler" technically refers to only one specific stage in the evolution of C++ code, in everyday conversation we also call the entire process (preprocessing, compiling, assembling, and linking) "compiling," and the whole toolchain a "compiler." This is a bit of a naming collision, but it is so deeply ingrained in the industry that there's no point fighting it. Just be aware of the context when someone says "compiler" — they usually mean the whole thing.
Practical Starting Point§
Windows§
The quickest starting point for C++ on Windows is to download Microsoft Visual Studio (NOT Microsoft Visual Studio Code — they are different products). Use the Visual Studio Installer. When prompted, select "Desktop Development with C++." After installing, create a new project using the "Console App" template.
The compiler that ships with Visual Studio is called MSVC (Microsoft Visual C++). It is a completely separate compiler from g++ and clang++ that Linux and macOS users use, but it does the same job. The code you write is the same; only the tool behind the scenes differs.
Linux and Mac§
The good news is that Linux and macOS (both Unix-like systems) make C++ development much more straightforward than Windows. You don't need a giant IDE — in fact, most C++ developers on Linux and macOS just use a plain text editor and the terminal. This is honestly a more honest way to learn C++, because you actually see what is happening under the hood instead of it being hidden behind a GUI.
Mac§
On macOS, the easiest way is to install what Apple calls "Xcode Command Line Tools." And don't worry — this is NOT the full Xcode application (which is 10+ GB and takes forever to download). The command line tools are much smaller and contain everything we need.
Open your terminal (you can find it by pressing Cmd + Space and typing "Terminal"), then type:
xcode-select --install
A popup will appear asking if you want to install the command line developer tools. Click "Install" and wait for it to finish. Afterward, verify the installation by typing:
clang++ --version
If you see a version number printed out, congratulations — you're ready. The compiler on macOS is clang++, which is part of the LLVM project (you don't need to know what that means right now, just remember the name).
Linux§
On Linux, it depends on your distribution. The most common ones are Ubuntu or Debian-based, and they use apt as the package manager. Open your terminal and type:
sudo apt update
sudo apt install build-essential
The build-essential package includes gcc, g++, make, and other essential development tools. After installing, verify by typing:
g++ --version
If you are using Fedora or another Red Hat-based distro, the package manager is dnf:
sudo dnf groupinstall "Development Tools"
And if you are on Arch (you probably already know what you're doing if you use Arch), just:
sudo pacman -S base-devel
Writing your first program§
Now that you have a compiler, you just need a text editor. You can use anything — literally anything that can edit text files. Windows Notepad can be used, although it is not recommended. Some popular choices are VS Code (which is different from Visual Studio, just to be clear), Sublime Text, or even nano or vim if you're feeling adventurous.
Create a file called main.cpp and put this inside (if you are using Visual Studio and following the steps above, main.cpp is already created for you — just edit the existing file):
#include <print>
int main()
{
std::print("Hello, World!\n");
return 0;
}
On Windows§
Find the green play button in the Visual Studio toolbar (it usually says "Local Windows Debugger") and click it. Visual Studio will automatically compile and run your program, and you should see "Hello, World!" appear in a console window that pops up. That green button is doing a lot of work behind the scenes — it runs the preprocessor, compiler, assembler, and linker for you, all in one click, which is exactly the kind of thing an IDE is good at hiding.
If it succeeds, the rest of this section will increase your knowledge of C++ but is not relevant for the Windows-specific workflow. If you're not interested, jump straight to the footnote at the very end. If it fails, go Google the error message — Visual Studio's error output is usually quite descriptive.
On Linux and Mac§
In your terminal, navigate to the folder where you saved the file and type:
g++ main.cpp -o hello
Or, if you are on macOS:
clang++ main.cpp -o hello
This will produce an executable file called hello. Notice there is no .exe extension — because, as mentioned earlier, on Unix-like systems the extension is irrelevant. What makes a file executable is its permission. The -o hello flag tells the compiler to name the output hello and automatically marks it as executable.
To run it, type:
./hello
The ./ part tells the shell "look in the current directory" — by default, Linux and macOS don't run programs from the current folder for security reasons, so you have to be explicit about it.
You should see Hello, World! printed to your terminal. Congratulations — you have just gone through the entire evolution of C++ code that we talked about earlier: preprocessing, compiling, assembling, and linking, all in one single command.
Where are the intermediate files?§
Now you might be wondering: where are the .i file, the .s file, the .o file that we talked about earlier? Well, the compiler deletes them after it's done, because usually we don't need them. But if you want to see them — which is a great way to really understand what we discussed — you can tell the compiler to stop at each stage:
# Stop after preprocessing — output is main.i
g++ -E main.cpp -o main.i
# Stop after compiling — output is main.s (assembly code)
g++ -S main.cpp -o main.s
# Stop after assembling — output is main.o (object file)
g++ -c main.cpp -o main.o
I highly recommend you try these out and open the files in your text editor. Seeing the actual assembly code or the preprocessed output will make all the abstract concepts we talked about suddenly feel very concrete and real.
A note on C++ versions§
The <print> header that we used is actually a very new addition to C++ — it was introduced in C++23. So you need a fairly recent compiler to use it. If you get an error saying print is not a member of std, you might need to tell the compiler to use the latest C++ standard by adding -std=c++23:
g++ main.cpp -o hello -std=c++23
If your compiler is too old and doesn't support C++23 at all, you can fall back to the traditional way of printing, which uses std::cout:
#include <iostream>
int main()
{
std::cout << "Hello, World!\n";
return 0;
}
This one works on pretty much any C++ compiler from the last 20 years, so if you're stuck on an older system, this is your friend.
Footnote§
Why is the compiler (apart from the preprocessor, assembler, and linker) the only C++-relevant part?
Preprocessor§
Fortran (another totally independent language) can use the exact same preprocessor — the one originally written for C, which C++ inherited. Fortran uses the preprocessor for the exact same reason and with the exact same advantage: to preprocess code before compiling. This is possible because the preprocessor doesn't actually understand C or C++ syntax at all — it just does text substitution. It doesn't care whether the text it's processing is C++, Fortran, or even something that isn't a programming language at all.
Assembler and Linker§
Every language that compiles down to native machine code (rather than to bytecode for a virtual machine, like Java or Python) ultimately needs to produce machine code and link those pieces together. Languages like Rust, Fortran, and Go all have their own assemblers and linkers that do essentially the same job as C++'s. As we explained above, this stage is architecture-dependent but not language-dependent — the linker doesn't know or care whether the object files came from C++, Rust, or Fortran; it just stitches them together.
Putting it together§
The preprocessor, assembler, and linker are generic tools that can be — and are — shared across many compiled languages. Only the compiler itself, the part that parses C++ syntax and translates it into assembly, is truly specific to C++. That is why, when we ask "what makes C++ C++?", the answer lives entirely in the compiler stage.
Discussion
No comments yet — yours could open the discussion.