Practical Guide to Bare Metal C++
In this technical retrospective (2016), @adamnemecek shares practical architectural lessons learned from running, scaling, or migrating production software under lean constraints.
Core Principles for Independent Builders:
Defaulting to simpler architectures (such as monolithic frameworks, SQLite, or managed VPS) prevents early engineering bankruptcy.
Evaluating true compute and egress costs reveals that high cloud bills can often be slashed by 80%+ through judicious re-platforming.
Code you write at 2 AM as a solo engineer must be easy to debug six months later without complex microservice dependencies.
Community Debate Themes:
HN engineers debated the tradeoffs between cloud convenience and raw server economics, database indexing strategies, and resisting premature architectural complexity.
CATEGORY Frugal Engineering →
HISTORICAL ERA Cloud & Mobile Era (2014–2019)
COMMUNITY SCORE 291 points
TOTAL DISCUSSIONS 83 comments
If you go down to micro-controller levels, you often times are stuck with C++ 2003 and a vendor specific compiler, which means you will lack many of the niceties in the article.
I agree with him that removing the entire standard library is needed. Of course you then need to copy an implementation of printf from somewhere, and likely set it up to only work in your debug builds. Then you quickly figure out that the standard library has a lot of things you didn't even realize you depended on. Math functions typically pop up next, you'll likely end up using a vendor library (by which I mean ARM's), but if you are doing a bunch of math heavy work, and even more so if your chip has some limited FP capabilities (assuming it has an FPU at all), you may also find the need to re-implement some functions based on your performance needs.
Embedded is fun. :)
All that said, I would kill for access to proper lambdas.
The usage of single throw statement in the source code will result in more than 120KB of extra binary code in the final binary image. Just try it yourself with your compiler and see the difference in size of the produced binary images.
What?? 120KB? I have to try that out right away.
Edit: That turned out to be completely wrong. Simple program without throw: 71'218 bytes. With throw (one additional line that throws a plain integer): 71'814 bytes. GCC 4.8.1 on Windows. Would have been surprised if that was true.
Edit2: Okay, my bad, I should have linked statically. The statement sounded so absolute, but of course it is conditioned on the bare metal environment which is the subject of this article. Thanks for pointing out where I went wrong.
He's talking about real time code here and completely missing one of the main reasons why you have to use great care using C++, and even parts of the C libraries ... all those hidden mutexes in new/malloc and other parts of the standard libraries (stdio too)
I'm sure we all know what happens when your ISR quietly does a new while some other part of your code is holding one of the locks deep in malloc.
But far more important is avoiding priority inversions when a low priority thread is holding a lock in a library somewhere, they result in high priority threads missing real-time deadlines - the sort of heisenbugs that are pretty impossible to find .... and are best to avoid by design.
Nice to see some concrete examples of where you don't want exceptions, rtti or dynamic memory.
Quite a few developers believe that if you don't have these features you aren't writing "real C++" but it's common practice in quite a few industries where size/performance is critical.
Template oriented programming is a tremendously useful technique for working with overhead-less abstractions for embedded software, but C++ makes it so hard. Most large users of template oriented development (automotive) use external code generators for what should ideally be supported by the language itself.