Article by Ayman Alheraki on January 31 2026 03:48 PM
A technical response to those who reduce the discussion to “C is simpler and has a stable ABI”
This article is not a theoretical preference or an abstract language debate. I am currently designing and implementing a real assembler in modern C++, as part of the ForgeVM toolchain, and this text reflects practical, hands-on engineering experience, not speculation.
The architectural decisions discussed here were made while building real components—lexer, parser, intermediate representations, backends, and emitters—under real constraints of correctness, extensibility, and long-term maintainability. What follows is therefore grounded in working code and evolving systems, not ideology.
Whenever the topic of building low-level system tools—such as an assembler—comes up, a recurring opinion appears:
“Write it in C. It’s simpler, faster, and has a stable ABI.”
This statement is not entirely wrong, but in many cases it is shallow and incomplete, ignoring the true nature of the project, its scale, its internal complexity, and its long-term evolution.
This article is not an emotional defense of C++, but a clear engineering explanation of what you actually gain when choosing modern C++ for building a real assembler—and why those gains cannot be replicated in C without paying a heavy architectural price.
A modern assembler is not:
a 500-line program,
a simple single-pass parser,
or a temporary educational tool.
A real assembler includes:
a Lexer,
a Parser,
semantic analysis,
symbol resolution,
an IR (even if minimal),
multiple ISA-specific backends,
emitters,
precise diagnostics,
and robust error recovery.
This is a full software system, not a “small utility.”
With modern C++, you can naturally design a clean architecture such as:
Lexer
Parser
IR
Backend per ISA
Emitter
Diagnostics
Using:
True polymorphism
Strong typing
RAII
Explicit ownership
Zero-cost abstractions
Each layer has a clear responsibility.
Transitions between stages are enforced by types.
Many errors are caught at compile time—not after weeks of debugging.
Trying to build the same system leads to:
structs everywhere,
function pointers,
manual lifetime management,
implicit, undocumented contracts,
reliance on discipline rather than language guarantees.
The result?
Bugs that consume weeks, not bugs that reveal themselves immediately.
Modern C++ does not imply GC or a heavy runtime. It provides:
Deterministic lifetime
Scope-bound resource management
A system that actively helps prevent self-inflicted damage
An assembler inherently involves:
complex state transitions,
symbol tables,
backpatching,
forward references.
The last thing you want in this context is:
“Did I forget to free this?”
With modern C++:
RAII removes this entire class of concerns,
resources are released automatically,
your mind stays focused on logic, not cleanup.
This is not luxury—it is stability and productivity.
A real assembler does not stay static:
Today: one ISA
Tomorrow: another ISA
Later:
optimizations,
macro systems,
debug information,
target-specific features.
Modern C++ scales with growth:
clean extensibility,
clear separation between frontend and backend,
genuine reuse.
C, over time, forces you to:
re-invent OOP patterns,
or accept architectural chaos,
or build an entire internal framework in a language not designed for it.
Yes:
C does provide a more stable ABI.
But the real question is:
Does an assembler need an ABI at all?
In most cases:
an assembler is a standalone tool,
it produces files, not a runtime,
it is not dynamically loaded as a shared library.
Meaning:
there is no ABI problem to begin with
and it is not solved by changing the language.
If an ABI is ever required:
expose a minimal C interface,
keep the core implemented in modern C++.
This is the professional solution—not a regression.
C is an excellent language—in its proper domain. However:
A multi-stage, architectural, long-lived assembler belongs in the domain of modern C++, not C.
Those who diminish modern C++:
often argue against 1990s C++,
or against misuse, not the language itself,
or project small-program assumptions onto large systems.
Engineers who build real system tools—and care about:
correctness,
scalability,
maintainability,
understand that:
A language is a tool. Choosing modern C++ here is an informed engineering decision—not ideology.