C++ Part I · Module 1 · Why C++ Matters in Semiconductor Engineering
C vs C++: What Carries Over and What Is Genuinely New
This is the chapter that decides whether an engineer arriving from C writes good C++ or merely writes C in files that end in .cpp. Two framings are common and both are wrong. 'C++ is C with classes' describes the language as it stood around 1983 and leads directly to the worst C++ in production: manual allocation, raw pointers with no stated owner, and types that are bags of fields with the rules living somewhere else. 'C and C++ are different languages' is wrong in the other direction, and it throws away the shared object and memory model that is the whole reason C++ can do the jobs Chapter 1.5 describes. The accurate position is more interesting than either. C++ grew out of C, kept almost all of its syntax and the entirety of its memory model, and is nonetheless not a superset of it — which this chapter proves with one file and two compilers. Then the part that matters most: six specific C habits, each one legal C++, each one a defect, each with the replacement named and the chapter that teaches it.
Foundation19 min readC++Part IFundamentalsC Interop
Part I · Module 1 · Chapter 1.4 · C vs C++
1. Two Wrong Answers
Ask ten engineers how C++ relates to C and you will get two answers, both confident and both damaging.
"C++ is C with classes." This is not a caricature — it is literally what the language was called before 1983, when it was renamed. As a description of C++ in the 2020s it is wrong by roughly forty years, and the people who hold it write a characteristic kind of code: malloc and free throughout, raw pointers passed around with no statement of who releases them, char* string handling, memcpy over anything, and structs whose rules live in whichever function last touched them. That code compiles. It is worse than the C it replaced, because it carries every one of C's hazards plus slower builds and worse error messages, and it buys nothing.
"C and C++ are different languages, don't mix them up." Wrong in the other direction, and it costs you the thing that makes C++ useful here. C++ shares C's entire object and memory model — an object is storage with a type at an address, with a lifetime, and nothing supervises it. That shared foundation is why C++ can model hardware data, meet a timing budget, and link against the C libraries that exist in every toolchain in this industry. An engineer who treats C++ as unrelated to C has no model for why a struct has padding in it, why a pointer can dangle, or why the DPI boundary in Module 28 works at all.
The accurate statement takes two sentences:
C++ evolved from C and retains its syntax, its object and memory model, its integer and pointer rules, its preprocessor and its standard library. On top of that foundation it adds its own type behaviour, initialization rules, references, overload resolution, templates, deterministic resource management, ownership patterns, standard library and lifetime reasoning — and those additions are not optional extras. They are what C++ is for.
The rest of this chapter makes both halves concrete.
2. What Carries Over
If you know C, this is the part you can stop worrying about. It transfers essentially intact.
The syntax. Statements, expressions, operators and their precedence, if/while/for/switch, function declaration and call syntax, braces and semicolons. A C function body is usually a valid C++ function body.
The object and memory model. An object is a region of storage with a type. It has an address. It comes into existence and later stops existing, and reading it outside that window is undefined. Chapter 1.1 §5 established this and it is worth restating here: the word "object" means typed storage, not "class instance".
Integer behaviour. The integer types, their implementation-defined widths, signed and unsigned, two's-complement representation, integer promotion, wraparound on unsigned and undefined overflow on signed — Chapter 1.3's demonstration used int overflow and would read identically in C. Module 3.3 covers it.
Pointers and arrays. Address-of, dereference, pointer arithmetic, arrays that decay to a pointer to their first element and therefore carry no length. Modules 8 and 9.
Structure layout. Members in declaration order, padding inserted for alignment, sizeof not equal to the sum of the members. Module 9.8.
The preprocessor. #include, #define, conditional compilation — all still there, all still textual, all still running before anything understands the language.
Separate compilation and linkage. Translation units, declarations versus definitions, the linker resolving symbols between object files. Module 24, and Chapter 1.7 builds the model.
The C standard library. Available, with the headers renamed: <stdint.h> is <cstdint>, <stdio.h> is <cstdio>, <string.h> is <cstring>. The C++ spellings put the names in namespace std, which is why this curriculum writes std::uint32_t and std::size_t. The old headers still work; prefer the new ones.
3. What Is Genuinely New
| Addition | What it gives you | Taught in |
|---|---|---|
| References | Another name for an existing object — not a pointer, cannot be null, cannot be rebound | 6.4, 8.5 |
| Classes with invariants | A type that can guarantee something about its own data, enforced by access control | Module 11 |
| Constructors and destructors | Code the language runs at the start and end of an object's lifetime, on every path | Module 12 |
| Deterministic resource management (RAII) | Cleanup owned by the type and guaranteed at a knowable point — Chapter 1.2's problem, solved | 12.9 |
| Copy and move semantics | You define what copying your type means, and transferring is distinguishable from duplicating | Module 13 |
| Ownership as a type-level statement | unique_ptr, shared_ptr — "who releases this" answered in the signature | Module 14 |
| Function overloading | One name, several parameter lists, resolved at compile time | 6.7 |
| Operator overloading | User-defined types that read like built-in ones | Module 15 |
| Templates | Code generated per type, so reuse does not cost type checking — Chapter 1.2's second problem, solved | Module 18 |
| The standard library | Containers, algorithms, strings, iterators — resource problems solved once, correctly | Modules 19, 20 |
| Exceptions | An error path that cannot be ignored by forgetting to check a return value | Module 22 |
| Namespaces | Names grouped so two libraries can coexist | 7.6 |
| Stricter type rules | Conversions C allows implicitly that C++ requires you to request | §4 and §7 below |
Read the "Taught in" column as a map of the curriculum rather than a reading list for today. The important observation is structural: the first six rows are all about object lifetime, and they are the reason Part I spends Modules 10 through 14 there. That is not an accident of ordering. Lifetime management is the thing C++ adds that C has no equivalent for, and nearly every other addition serves it.
4. C++ Is Not a Superset of C
This claim gets made loosely in both directions, so here it is settled with one file.
#include <stdlib.h>
#include <string.h>
int main(void)
{
/* 1. implicit void* -> int* conversion */
int *values = malloc(4 * sizeof *values);
/* 2. a string literal bound to char* */
char *name = "addr_reg";
/* 3. `new` is an ordinary identifier in C */
int new = 7;
values[0] = new + (int)strlen(name);
free(values);
return 0;
}As C17, with every warning this curriculum uses:
clang -std=c17 -Wall -Wextra -Wpedantic csample.c → exit 0, no diagnosticsNow copy that file — byte for byte, no edits — to csample_as.cpp and compile it as C++17:
clang++ -std=c++17 csample_as.cpp
csample_as.cpp:7:10: error: cannot initialize a variable of type 'int *'
with an rvalue of type 'void *'
csample_as.cpp:13:9: error: expected unqualified-id
csample_as.cpp:15:21: error: expected a type
3 errors generated.Three errors from a file that is impeccable C. Each one is a deliberate C++ decision:
void* does not convert implicitly. In C, malloc's void* return becomes an int* silently. C++ refuses, because silent pointer-type conversion is exactly the mechanism that erased the type information in Chapter 1.2 §3. If you mean it, you must say static_cast<int*>(...) — and the fact that you have to say it is the feature.
new is a keyword. C++ took several identifiers that are ordinary words in C: new, delete, class, template, this, namespace, try, catch, operator, virtual. Any C code using one as a variable name will not compile, which is a real obstacle when wrapping an existing C library.
The third error at line 15 is a knock-on from line 13 — once the declaration failed, the name has no type.
And the fourth difference, which is the interesting one because it is not an error at default settings:
int main()
{
char* name = "addr_reg"; // string literal is const char[9] in C++
(void)name;
}clang++ -std=c++17 strlit.cpp
warning: ISO C++11 does not allow conversion from string literal
to 'char *' [-Wwritable-strings]
exit 0
clang++ -std=c++17 -pedantic-errors strlit.cpp
error: ISO C++11 does not allow conversion from string literal
to 'char *' [-Werror,-Wwritable-strings]
exit 1In C, a string literal is an array of char and binding it to a char* is normal. In C++ it is an array of const char, and the conversion was deprecated in C++98 and removed in C++11. Clang accepts it anyway, with a warning, unless you ask it not to.
This is precisely the situation Chapter 1.3 §6 described: the standard says one thing, the compiler accepts another, and only a flag tells you which you relied on. It is also why writing through such a pointer is a classic crash — the characters may live in read-only storage.
5. The Habit That Causes the Most Damage
Of everything in this chapter, one habit is worth a section of its own, because it is the difference between a C++ codebase that works and one that leaks.
In C, when a function needs to give the caller something it allocated, it returns a pointer. Who frees it is settled by a comment, a naming convention, or the documentation:
/* Caller owns the returned buffer and must free() it. */
unsigned *make_vector_buffer(size_t count);That comment is the entire ownership specification. It is not checked. It does not travel with the pointer. A caller who does not read it leaks, and a caller who reads it wrongly double-frees — and Chapter 1.2 §2 showed how quickly the number of paths that must get it right grows.
In C++ the same intent is expressed in the type, which means the compiler and the reader both see it:
// Returns an owning handle. When it goes out of scope, the memory is released.
// Nothing to remember, nothing to document, no path that can forget.
std::unique_ptr<std::uint32_t[]> make_vector_buffer(std::size_t count);You have not been taught std::unique_ptr and should not try to use it yet — Module 14 is where it belongs, and it depends on move semantics from Module 13. It appears here for one reason: so you can see that ownership moved from a comment into the type system. That transition is the single largest practical difference between C and C++ in day-to-day engineering work, and it is what Modules 10 through 14 of this curriculum exist to deliver.
6. malloc Does Not Create an Object
Here is the same habit producing a sharper failure, and this one is worth seeing run.
In C, malloc gives you storage and you use it. In C++, a type can have initialisation attached to it — and malloc knows nothing about that. Consider the RegisterCheck from Chapter 1.1, now with each member given a default value, and actual given a recognisable sentinel:
struct RegisterCheck {
std::uint32_t address = 0u;
std::uint32_t expected = 0u;
std::uint32_t actual = 0xDEADBEEFu; // sentinel: "nothing has written this yet"
};
int main()
{
// The C++ way: new creates an OBJECT, so the member initialisers run.
RegisterCheck* made = new RegisterCheck();
show("new RegisterCheck()", *made);
delete made;
// The C habit: malloc hands back STORAGE. No object was created here, so
// no member initialiser ran. Reading through this pointer is undefined
// behaviour; the bytes are pre-set to a recognisable pattern only so the
// observation below is legible.
void* raw = std::malloc(sizeof(RegisterCheck));
std::memset(raw, 0x5A, sizeof(RegisterCheck));
RegisterCheck* faked = static_cast<RegisterCheck*>(raw);
show("malloc + cast (UB)", *faked);
std::free(raw);
}Observed output on the machine used for this chapter:
new RegisterCheck() address=0x00000000 actual=0xdeadbeef
malloc + cast (UB) address=0x5a5a5a5a actual=0x5a5a5a5aThe first line is what the type promised. The second line is the storage, untouched by anything the type had to say about itself — the 0x5A pattern is simply what was written into the buffer by memset, and the member initialisers never ran at all.
The rule to carry forward, which costs nothing to follow now and is justified properly in Module 12: in C++, do not use malloc and free on types that are not plain aggregates of scalars — and prefer not to use them at all. Module 10 teaches new and delete honestly, Module 10.8 shows why even those do not survive real control flow, and Module 14 gives you the tools that make raw allocation rare.
7. One Cast Syntax Was Hiding Four Operations
The last of the three demonstrations, and the one with the most startling numbers.
C has one cast syntax, (T)expr, and it performs whatever conversion is needed to get from here to there. That convenience hides a serious ambiguity, because "whatever is needed" covers operations with completely different meanings and risks. Watch:
std::uint32_t word = 0x3F800000u; // the bit pattern of 1.0f
// The C habit: one syntax for every conversion. This one reinterprets.
float reinterpreted = *(float*)&word;
// The C++ habit: say which conversion you mean. This one converts the VALUE.
float converted = static_cast<float>(word);Expected output:
C-style cast -> 1.0
static_cast -> 1065353216.0Two radically different answers from the same input, and neither is a mistake in itself. 0x3F800000 is the IEEE-754 bit pattern for 1.0f, so reinterpreting the bytes as a float gives 1.0. Converting the value — the integer 1,065,353,216 — to float gives 1,065,353,216.0. Both are things an engineer sometimes wants. The problem is that in C both are spelled the same way, so the source does not record which one the author meant.
C++ splits the operation into named casts, and static_cast will not perform a reinterpretation even if you ask it to:
clang++ -std=c++17 -c ch14_cast_bad.cpp
error: static_cast from 'std::uint32_t *' (aka 'unsigned int *')
to 'float *' is not allowedThat refusal is the whole point. static_cast performs value conversions and related-pointer conversions; a bit-level reinterpretation requires reinterpret_cast, which is a different word that a reviewer will notice. Dropping const requires const_cast. Asking about a type at run time requires dynamic_cast.
Module 4.10 teaches the conversion model and static_cast properly; dynamic_cast waits for Module 17.8, where there is a class hierarchy to ask about. For now, one rule: in C++, do not use C-style casts. Not because they fail to work, but because they do not say what you meant, and the thing they silently do instead is sometimes a reinterpretation of memory.
8. The Six Habits, With Their Replacements
Everything above, condensed into the form you can use in review. Each row is legal C++ that compiles.
| C habit | What goes wrong in C++ | Reach for instead | Taught in |
|---|---|---|---|
malloc / free on a non-trivial type | No object is created, so initialisation never runs; destruction never runs either | new at first, then stop allocating by hand | 10.3, Module 14 |
| Raw pointer returned, owner in a comment | Nothing checks the comment; every caller is a chance to leak or double-free | Ownership stated in the type | Module 14 |
memcpy over any type | Copies bytes past a type that needed its own copy logic; silently corrupts a std::string or std::vector | Let the type define copying | Module 13 |
char* strings with manual length | Terminator and buffer-size bugs; the three classic string failures | std::string, std::string_view | 9.5, 9.6 |
| C-style casts | One syntax for four operations; a value conversion and a memory reinterpretation look identical | static_cast, and name any other | 4.10 |
struct plus free functions, rules elsewhere | Nothing can guarantee the data is consistent; every caller must apply the rules correctly | A class with an invariant | Module 11 |
One further habit is worth naming separately because it is about judgement rather than syntax: error reporting by returned status code that the caller may simply not check. C has no alternative. C++ has several — exceptions (Module 22), std::optional (21.5), and status types — and choosing among them is a design decision rather than a default. 22.1 is where that choice is made properly.
9. Why Interviewers Probe This
This chapter carries the module's only Critical interview weighting, and it is worth knowing why — not as exam prep, but because the reason is a real signal.
An interviewer asking "what is the difference between C and C++?" is almost never testing whether you can list features. They are testing whether you understand resource ownership, because that is the difference that shows up in production code. A candidate who answers "C++ has classes, templates and the STL" has listed the syntax. A candidate who answers "C++ lets a type own a resource and release it deterministically, so cleanup stops being the caller's problem" has described the thing the language was built for — and will be asked a good follow-up rather than a remedial one.
The same applies to the sharper versions of the question: why is malloc wrong here, what does static_cast refuse to do, who frees this pointer. Each one is asking whether you reason about lifetime and ownership, or only about syntax.
10. What You Should Be Able to Say Now
- C++ grew out of C, keeps its syntax and its entire object and memory model, and adds its own type behaviour, lifetime management, references, overloading, templates, standard library and ownership patterns on top.
- "C++ is C with classes" describes the language before 1983 and produces the worst kind of modern C++.
- C++ is not a superset of C. The same file compiled with zero diagnostics as C17 and produced three errors as C++17.
void*does not convert implicitly in C++, several C-legal identifiers are C++ keywords, and a string literal isconst char[]— the last of which is a warning by default and an error with-pedantic-errors.- The largest practical difference is that ownership moves from a comment into the type system.
mallocallocates storage;newallocates storage and creates an object, which is when initialisation runs. Observed: initialisers ran fornewand did not formalloc+ cast.- Reading through that
malloc+ cast is undefined behaviour, and the damage scales with the type — catastrophic for anything holding internal pointers. - A C-style cast hides four different C++ operations. The same
0x3F800000gave1.0through a reinterpreting cast and1065353216.0throughstatic_cast, andstatic_castrefuses the pointer reinterpretation outright. - Six C habits are legal C++ and defective: hand allocation of non-trivial types, ownership in comments,
memcpyover any type,char*strings, C-style casts, and data separated from its rules. - C and C++ link together deliberately, via
extern "C"— Module 24.5 for why, Module 28.6 for the DPI boundary.
11. Exercises
Exercise 1 — fix the C file. Take §4's csample.c and make the smallest set of changes that compiles as C++17 with -Wall -Wextra -Wpedantic -pedantic-errors. You should need four edits. For each, say whether you were satisfying a rule or working around a keyword.
Exercise 2 — explain the asymmetry. C allows void* to convert to int* implicitly; C++ requires a cast. Using Chapter 1.2 §3, argue why C++ made that stricter, and name the specific bug class the strictness prevents.
Exercise 3 — raise the stakes. §6's demonstration used a type holding three integers. Describe, step by step, what would go wrong if the type held a std::string instead — specifically what the string's internal state would be and what the first operation on it would likely do.
Exercise 4 — read the two casts. Without running anything, say what each of these produces for std::uint32_t bits = 0x40490FDBu; and which of the two an engineer decoding a floating-point register value wants: (a) *(float*)&bits, (b) static_cast<float>(bits). Then name the C++ cast that should be used for the one you chose.
Exercise 5 — audit a signature. A colleague's header contains:
std::uint32_t* decode_packet(const std::uint8_t* raw, std::size_t len, std::size_t* out_words);
Apply §8's test question. List every thing a caller must get right that the signature does not state, and sketch what the C++ version of the same interface would say instead. You do not need to write valid code for the replacement.
Exercise 6 — find the C in the C++. Below is legal C++17 that compiles. Name every habit from §8 that appears, and say what each one costs.
struct Txn { std::uint32_t addr; std::uint8_t* data; std::size_t len; };
Txn* make_txn(std::uint32_t addr, const std::uint8_t* src, std::size_t len)
{
Txn* t = (Txn*)malloc(sizeof(Txn));
t->addr = addr;
t->data = (std::uint8_t*)malloc(len);
memcpy(t->data, src, len);
t->len = len;
return t; /* caller must free t->data and then t */
}Exercise 7 — answer the interview question well. In three sentences, answer "what is the difference between C and C++?" in the way §9 describes as strong. Do not list more than one feature by name.
12. Summary
- The accurate relationship: C++ evolved from C, retains its syntax and its whole object and memory model, and adds type behaviour, lifetime management, references, overloading, templates, a standard library and ownership patterns that are the point rather than extras.
- Neither easy framing survives. "C with classes" produces C in
.cppfiles; "unrelated languages" throws away the shared memory model that makes C++ useful for hardware-adjacent work. - C++ is not a superset of C — one unmodified file, zero diagnostics as C17, three errors as C++17: implicit
void*conversion,newas an identifier, and the knock-on. Thechar*string literal is a fourth difference that is a warning by default and an error with-pedantic-errors. - Ownership moved from comments into types. That is the single largest day-to-day difference, and Modules 10 to 14 are built to deliver it.
mallocallocates storage;newcreates an object. Measured:newran the member initialisers (actual=0xdeadbeef),mallocplus a cast did not (actual=0x5a5a5a5a) — and that read is undefined behaviour whose cost scales with the type.- One cast syntax hid four operations.
1.0versus1065353216.0from the same bits, andstatic_castrefuses the reinterpretation that a C-style cast performs silently. - Six habits to retire, each legal and each defective: hand allocation of non-trivial types, ownership in comments,
memcpyover any type,char*strings, C-style casts, data separated from its rules. - The review question that finds most of them: which type is responsible for this, and does its signature say so?
You now know what C++ is, why it exists, which version this curriculum holds itself to, and how it relates to the language most of this field's existing code is written in. What remains unanswered is the practical question an engineer is entitled to ask before investing in a language: where is it actually used in this industry, and for what? Chapter 1.5 answers that concretely — not with a list of job titles, but with the specific places C++ appears in a semiconductor flow and the property of the language that puts it there.
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.
