C++ Part I · Module 1 · Why C++ Matters in Semiconductor Engineering
Why C++ Exists and What Problem It Solves
Chapter 1.1 ended on a question it could not answer. If C++ sits on the same side of the line as C, shares its memory model, and adds abstractions that mostly compile away, what was the actual difficulty that justified building a new language? The answer is not 'C needed classes'. C++ exists because two properties that had always been traded against each other both turned out to be necessary at once: the organisational power of a language with genuine user-defined types, and the complete absence of a managed runtime. Everything that is good about C++ and most of what is awkward about it follows from refusing to give up either. This chapter shows the two problems in working C code — code that compiles without a single warning and still cannot be made safe — and then states the design constraints the solution had to satisfy. Knowing those constraints is what turns the rest of this curriculum from a list of features into a set of decisions.
Foundation15 min readC++Part IFundamentalsDesign Rationale
Part I · Module 1 · Chapter 1.2 · Why C++ Exists and What Problem It Solves
1. The Trade Everyone Used to Accept
Through the 1970s, a working engineer choosing a language for a substantial system faced a trade that felt like a law of nature.
You could have a language that helped you organise a large program — one with user-defined types that behaved like real types, so that a concept in your problem became a concept in your code. Simula was the canonical example. The price was a runtime: machinery that sat between your program and the machine, managing memory and dispatch, costing time and space you did not control.
Or you could have a language that got out of the way — one where what you wrote was essentially what ran, with no managed layer underneath. C was the canonical example. The price was that your abstractions were conventions rather than language constructs. A "transaction" was a struct plus a naming convention plus a set of functions you had to remember to call in the right order, and nothing in the language knew those things belonged together.
For a program of a few thousand lines, the second option is clearly right and the first looks like overhead. For a program of a few hundred thousand lines that must also meet a performance budget, neither option is acceptable, and that is the situation C++ was designed for. Bjarne Stroustrup's work at Bell Labs in the late 1970s and early 1980s ran into exactly this: he had used Simula to model a distributed system and found the organisation invaluable and the performance unusable; he had used a C-family language and found the performance fine and the program unmanageable.
The question C++ answers is therefore narrow and concrete:
Can a language give you the organisational power of user-defined types while leaving no runtime machinery you did not ask for?
The next two sections are what the absence of that answer actually cost.
2. Problem One: Nothing Ties Cleanup to Control Flow
This is the most consequential of the two, and the one that shaped C++ most deeply.
Consider a function that has to acquire several resources and can fail partway through — the shape of nearly every real function that touches the outside world. Here it is in C: open a file holding test vectors, read a count, allocate a buffer, fill it.
int load_vectors(const char *path, unsigned **out, size_t *out_count)
{
FILE *f = fopen(path, "r");
if (f == NULL) {
return -1; /* nothing acquired yet */
}
size_t count = 0;
if (fscanf(f, "%zu", &count) != 1) {
fclose(f); /* release 1 of 1 */
return -1;
}
unsigned *buf = malloc(count * sizeof *buf);
if (buf == NULL) {
fclose(f); /* release 1 of 2 */
return -1;
}
for (size_t i = 0; i < count; ++i) {
if (fscanf(f, "%u", &buf[i]) != 1) {
free(buf); /* release 2 of 2 */
fclose(f);
return -1;
}
}
fclose(f);
*out = buf;
*out_count = count;
return 0;
}This function is correct. That is the point. It compiles cleanly under -Wall -Wextra -Wpedantic, it releases every resource on every path, and an experienced C programmer writes it without thinking hard.
Now count what correctness required. There are four places this function can return and four separate fclose calls, one per exit. The free appears on the one path where a buffer exists and the function still fails — and on that path it must be accompanied by fclose as well. The cleanup for each resource is not written once; it is written once per exit path that needs it, and the author has to determine the right subset by hand each time.
Here is why that is a language problem rather than a discipline problem:
- The cost grows with the product, not the sum. Add a second buffer and a mutex and you have four resources and six exit paths, and the author is now choosing between up to four releases at each of six places.
- Every future edit is a chance to get it wrong. Insert a new early return six months later and you must re-derive which resources are live at that point. Nothing in the language will tell you if you guess wrong; the program simply leaks, usually without any visible symptom until a long-running process runs out of something.
- The compiler cannot help. There is no declaration anywhere in that function saying "this file handle must be closed exactly once". The pairing exists only in the author's head and in the reviewer's attention.
C programmers developed real disciplines for this — the single-exit goto cleanup pattern is the best of them, and it genuinely reduces the duplication. But it is a convention. It is not checked, it is not composable, and it does not survive a second kind of early exit.
This single problem explains a surprising amount of C++'s design. Deterministic destruction — the guarantee that cleanup happens at a knowable point rather than whenever a collector decides — is exactly what a garbage-collected language cannot offer, and it is the reason C++ did not adopt one. Chapter 1.1 §8 listed that determinism as something C++ does better than a managed language rather than merely differently. This is why.
3. Problem Two: Abstraction Costs You Type Checking
The second problem is subtler and produces worse bugs.
In C, the way to write one piece of code that works for many types is to erase the type — pass void * and a size, and let the caller cast. The standard library's own sort routine works this way. Here is a program that sorts an array of 32-bit addresses:
static int compare_ints(const void *a, const void *b)
{
int x = *(const int *)a;
int y = *(const int *)b;
return (x > y) - (x < y);
}
int main(void)
{
unsigned addresses[] = { 0x40001000u, 0x90000000u, 0x40002000u };
size_t n = sizeof addresses / sizeof addresses[0];
qsort(addresses, n, sizeof addresses[0], compare_ints);
for (size_t i = 0; i < n; ++i) {
printf("0x%08X\n", addresses[i]);
}
return 0;
}The array holds unsigned values. The comparator reads them as int. Run it:
0x90000000
0x40001000
0x400020000x90000000 sorted to the front, before two smaller addresses.
Work through why, because the mechanism matters more than the outcome. 0x90000000 has its top bit set. Read as an unsigned, that is 2,415,919,104 — the largest of the three. Read as a 32-bit signed int, the same bit pattern is a negative number. The comparator was told these were ints, so it compared a negative value against two positive ones and dutifully reported that it belonged first. The sort did precisely what it was asked.
Now the part that makes this a language problem. Compile that with -Wall -Wextra -Wpedantic and there is not one warning. The compiler could not object: qsort's fourth parameter is a pointer to a function taking two const void *, and compare_ints is exactly that. The type mismatch is real, but it was erased at the boundary — by the time the comparator receives its arguments, the information that would have caught the error no longer exists in the program.
This is the general shape of the problem: in C, the mechanism for writing reusable code requires discarding the types, and discarding the types discards the checking. Every void * interface has this property. Every macro-based container has a version of it. The abstraction and the safety were in tension, and C resolved the tension by dropping the safety.
Here is the same task in C++:
#include <algorithm>
#include <cstdint>
#include <iomanip>
#include <iostream>
#include <vector>
int main()
{
std::vector<std::uint32_t> addresses{0x40001000u, 0x90000000u, 0x40002000u};
std::sort(addresses.begin(), addresses.end());
for (std::uint32_t a : addresses) {
std::cout << "0x" << std::hex << std::uppercase
<< std::setw(8) << std::setfill('0') << a << '\n';
}
}Expected output:
0x40001000
0x40002000
0x90000000Correct, and correct for a structural reason rather than a lucky one. std::sort is a template: it is not one function that erases types, it is a pattern from which the compiler generates a function for the specific types you used. Inside the generated function, the elements are still std::uint32_t. There is no point at which the type was thrown away, so there is no point at which the checking could be lost.
And the mistake from the C version? Write a comparator for the wrong type and hand it to std::sort:
std::vector<std::uint32_t> addresses{0x40001000u, 0x90000000u, 0x40002000u};
// Comparator written for a different type entirely.
std::sort(addresses.begin(), addresses.end(),
[](const std::string& a, const std::string& b) { return a < b; });The compiler rejects it:
error: no matching function for call to object of type
'(lambda at ch12_sort_bad.cpp:12:15)'The error arrives at compile time, before the program exists. In the C version the equivalent mistake compiled silently and produced a plausible-looking wrong answer — which, in a verification context, is the worst possible failure mode: a sorted-looking list of addresses that is not sorted, feeding a comparison that now reports a mismatch no one can explain.
4. The Constraints the Answer Had to Satisfy
C++ did not get to solve those two problems in a vacuum. It had to solve them under constraints that were, at the time, unusual — and those constraints are why C++ looks the way it does.
Compatibility with C, as close as practical. A new language that could not call existing C code or be linked against existing C libraries was not adoptable. This decision bought C++ an installed base and cost it the freedom to fix C's genuine mistakes. Chapter 1.4 is the full inventory of what that compatibility retained, including the parts a modern C++ programmer should never use.
No managed runtime. No garbage collector, no interpreter, no mandatory layer between the program and the machine. This is what makes the determinism in §2 possible, and it is what makes C++ usable in the places Chapter 1.5 describes.
You do not pay for what you do not use. A program that uses no virtual functions must carry no dispatch machinery. A program that throws no exceptions must not be slowed by the possibility. This is the zero-overhead principle, and it is a stronger claim than the "zero-cost abstraction" of Chapter 1.1 §6: the first is about features you ignore costing nothing, the second is about features you use compiling away.
An abstraction should be as efficient as the hand-written equivalent. If using std::vector were meaningfully slower than managing an array yourself, engineers in performance-sensitive work would not use it, and the organisational benefit would be lost to exactly the people who needed it most. Chapter 1.1's measurement is this constraint being met.
Static type checking, strengthened rather than weakened. The answer to §3 could not be "a runtime type system". It had to be checking that happens during translation, which is what pushed C++ towards templates rather than towards type erasure.
Those five constraints, taken together, are a hard problem. They are also the honest source of C++'s complexity:
| Constraint | What it bought | What it cost |
|---|---|---|
| C compatibility | Adoption, interoperability, access to existing libraries | Implicit conversions, arrays that decay to pointers, a preprocessor, C's integer-promotion rules |
| No managed runtime | Deterministic destruction, predictable timing, hardware-adjacent work | Manual lifetime reasoning; nothing catches a dangling reference for you |
| Pay only for what you use | Usable in constrained and real-time contexts | Many features are optional and interact, so the language has more surface |
| Abstraction as efficient as hand-written | Engineers in performance work can actually use the library | Heavy reliance on inlining and templates, which costs compile time and error-message clarity |
| Static checking strengthened | Mistakes caught before the program runs | Template-instantiation errors, and a type system with real depth to learn |
Read that table as the answer to "why is C++ like this?" Almost nothing in the right-hand column is an accident or an oversight. It is the price of the left-hand column, paid deliberately.
5. Why This Still Decides Things in Your Code
This is history with immediate consequences, not background colour.
It tells you what to reach for first. If the language exists to let you build types that manage their own resources and check their own invariants, then a design where data is a bag of fields and the rules live in whichever function last touched it is working against the language. That is the single most common defect in C++ written by experienced C programmers.
It tells you where to spend effort. Because the checking happens during translation, effort spent on types — choosing a specific one, making a wrong combination unrepresentable, marking what must not change — pays off permanently and costs nothing at run time. Effort spent on run-time validation of things the type system could have guaranteed is effort spent twice and still less reliable.
It tells you how to read the standard library. std::vector, std::unique_ptr and std::string are not conveniences added for comfort. Each one is a resource problem from §2 solved once, correctly, by a type — so you never write that cleanup again. Seeing them that way is the difference between using the library and merely knowing its names.
And it tells you what C++ will not do for you. The constraints that produced deterministic destruction also produced the absence of a safety net. Chapter 1.1 §8 listed that honestly; now you know it was a trade rather than a gap.
6. What You Should Be Able to Say Now
- Before C++, a language either helped you organise a large program or stayed out of the machine's way. C++ exists because large systems with performance budgets need both.
- In C, cleanup is written once per exit path that needs it, not once per resource. The cost grows with the number of resources times the number of exits, and no part of the language checks the pairing.
- C++'s answer to that is to give a type responsibility for releasing what it holds, at a guaranteed point in its lifetime. The idiom is called RAII and Module 12 teaches it.
- Deterministic destruction is why C++ has no garbage collector. A collector releases memory at an unspecified later time, which is useless for a lock, a file handle or a simulator resource.
- In C, reusable code is written by erasing types through
void *, and erasing the type erases the checking. Theqsortexample compiled with zero warnings and sorted0x90000000to the front. - C++ answers that with templates: code generated per type, so the type information — and therefore the checking — is never discarded.
- The equivalent C++ mistake is a compile-time error, but it is reported at the point of instantiation, often inside library headers. That is a real cost;
concepts(C++20) exists to improve it. - C++'s five design constraints were C compatibility, no managed runtime, pay only for what you use, abstraction as efficient as hand-written code, and strengthened static checking.
- Most of what is awkward about C++ is the price of one of those constraints, paid deliberately.
7. Exercises
Exercise 1 — count the cost of a fifth resource. The function in §2 holds two resources across four exit paths. Suppose you add a mutex that must be unlocked and a second buffer that must be freed. Without writing the code, state how many exit paths the function could reasonably have and what the author must decide at each one. Then say which of C++'s two problems from this chapter that growth illustrates.
Exercise 2 — explain the sort result. Using the bit pattern, explain in your own words why 0x90000000 compared as less than 0x40001000. Then state the one-word change to compare_ints that fixes it, and explain why the compiler could not have suggested that change itself.
Exercise 3 — the warning that could not exist. The qsort program compiles with -Wall -Wextra -Wpedantic and no diagnostics. Explain precisely what information the compiler would have needed to warn you, and at exactly which point in the program that information stopped existing.
Exercise 4 — distinguish the two principles. Chapter 1.1 §6 demonstrated "zero-cost abstraction"; §4 above names the "zero-overhead principle". State the difference in one sentence each, and give an example of a C++ feature for which only one of the two applies.
Exercise 5 — argue the trade. A colleague proposes that C++ would be a better language if it had a garbage collector, since most of its bugs are lifetime bugs. Using §2, give the strongest argument against — and then name one thing their proposal would genuinely have improved. A good answer concedes the second part.
Exercise 6 — read the constraints backwards. Pick two rows from §4's table. For each, describe a specific piece of C++ you have heard complained about, and connect it to the constraint that produced it.
8. Summary
- C++ exists to hold two properties that used to be exclusive: the organisational power of real user-defined types, and no managed runtime between the program and the machine.
- Problem one was cleanup. In C it is written per exit path, not per resource, nothing checks the pairing, and every future edit can break it. C++'s answer is RAII — deterministic release owned by the type, taught in Module 12.
- Determinism is why there is no collector. Cleanup at a knowable point is what lets C++ manage locks, files and handles, not just memory.
- Problem two was abstraction versus checking. C's
void *route to reuse erases the type and therefore the checking — demonstrated by a zero-warning program that sorted0x90000000first. - Templates are the answer to problem two: code generated per type, so checking is never discarded. The cost is error messages reported at instantiation, which
concepts(C++20) addresses. - Five constraints shaped everything: C compatibility, no managed runtime, pay only for what you use, abstraction as cheap as hand-written code, strengthened static checking. Most of C++'s awkwardness is one of those bills.
- This history is operational. It tells you to build types that own their resources, to spend effort on the type system rather than on run-time re-validation, and to read the standard library as a catalogue of resource problems already solved.
Both of this chapter's demonstrations rested on something unstated. The qsort program's behaviour depended on how a 32-bit pattern is read as a signed integer; the C++ error message came from one particular compiler at one particular version; and the phrase "valid C17" appeared without anyone saying which C++ this curriculum will hold itself to. Those are all the same question — what exactly is the language we are agreeing on, and what is merely what this machine happens to do? Chapter 1.3 fixes the baseline and gives you the vocabulary to tell the two apart, which every claim in the next twenty-seven modules depends on.
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.
