A subset of the modern freestanding C++. My Holy Grail for the hobbyist programming?
I was super busy with my Robotics education at Stanford, and hadn’t been posting anything in this blog for a long time. I hope to make posts about the things which I learned there soon, but today I would like to continue the topic started in the previous post, by implementing a minimal Hello World program using modern, freestanding (w/o libc) C++ on x86_64 Linux.
Thoughts on why C++
As I mentioned in the previous post - when you need to write a small patch for a DOS program it makes sense to stick to the good old assembly and a COM executable, but what if you are going to implement something more complex, something that you want to be able to port to a different CPU architecture or a different Operating System and still avoid the bloat? A higher level language like C or C++ might become necessary. So why would I consider choosing C++ over more lean and implicit C? After working several years with C++ professionally and following some of it’s later developments I finally do believe in the “good subset of C++” which in combination with certain compiler/linker flags can represent a personal “Holy Grail” for the hobbyist programming. The exact subset is a matter of individual taste and it requires time before you’ll be able to isolate such subset for yourself. Here are some of my preferences:
C++ language features which I find useful
- Compatibility with C, I still can have the pointer arithmetic and all the low level freedoms.
- Namespaces. Really, why live without them in 2026?
- constexpr
- The resource ownership concept facilitated by having more control and
desired implicit behavior:
– The ability to prohibit the copy(and the move) of an object.
– The move semantics and move-only classes
– Destructors and the automatic destruction of an object allocated on the stack at the exit from the scope. - Language support of inheritance, member functions and that the virtual functions are explicitly opt-in. (Oftentimes OOP is the right tool for the task).
- Attributes, like [[nodiscard]] or [[deprecated]]
- Templates and template meta programming techniques. (Occasionally)
- Formal copy elision guarantees (since C++17)
I’d be perfectly fine without these C++ language features
- The “passing by reference” concept. I prefer a const non-null pointer enforced at the compile time and passing variables by value as in C. It requires you to take the address of the variable and dereference the pointer in the function body, which makes it more visible that the passed variable is not a copy.
- Exceptions - prohibited by many style guides, especially in the safety-related industries.
- Operator overloading. Eigen and other good libs can have it, but I could survive without it for the sake of keeping the code more explicit. (a function call should look like a function call).
- Implicit constructors by default - I’d prefer having no magic conversions.
- The ability to have a non
=defaultimplementation of the default constructor in a class without a destructor. I don’t want to have any hidden logic If everything I want is to just allocate the memory to be initialized later, especially when there is no visual clue that some behavior is being executed.
De-bloating your C++ program
A lot of the resulting binary size can be reduced by tweaking the compiler & linker flags. Most of that involves the sections alignment and manipulating the headers in the resulting executable. Some of these settings are security trade-off’s, for example, we can combine the code section with the ro/data sections (having more permissive memory access attributes in the combined segment) or disable the stack protector (a technique used to detect stack overflow by placing a magic number on the stack as a canary) - obviously you need to be careful if decide to disable these features.
Another big thing which we can get rid of is linking our program with the C standard library (again achieved via compiler and linker directives). Do we need libc to make a working program? We absolutely don’t, we can implement only those functions which our program needs and luckily C++ has a concept for that called the freestanding implementation, which is used in the embedded programming industry. Keep in mind, though, that the stack protection mentioned earlier will require function/global variables defined in libc, so either disable it (if you write a low security, hobbyist project) or be ready to re-implement them yourself.
We can check whenever the code is compiled in the freestanding (or hosted)
implementation in the #if pre-processor directive by checking
__STDC_HOSTED__ == 0
Each program should have a starting point, called _start().
Without the libc we need to implement it ourself:
#if __STDC_HOSTED__ == 0
int main();
inline void _exit(const int status);
extern "C" {
void _start() { _exit(main()); }
}
#endif
The function name should not be mangled by the C++ compiler,
thus need extern "C".
Each program should make a syscall at the end, to finish the execution and
return an exit code, let’s define an implementation of this syscall in the
_exit function for the target platform.
#if defined(__x86_64__)
inline void _exit(const int status) {
asm volatile("syscall"
:
: "a"(60), "D"((long)(status & 255))
: "rcx", "r11", "memory");
}
#endif
I recommend to write as little asm code as possible, to allow the compiler to do the proper optimizations (such as using xor to set the registers to zero instead of using any hand-coded mov’s).
What left is a minimal main():
int main() {
return 0;
}
Any other low level functions (such as writing to a file) can be
implemented as needed and will look very similarly (syscall wrappers).
I find this practice advantageous as it allows you to have a modern,
better C++ API for the old C functionality in your project
(you can have proper namespaces, the error code returned from the function
instead of a global variable, and have the error checking be
enforced by [[nodiscard]] attribute).
The size of a Hello World program
As an exercise I made a Hello World implementation using the modern freestanding
C++: GitHub link
The majority of the code is just scaffolding and CMake script with a bunch of
flags aimed to make the project cross platform. On the systems like OpenBSD
the freestanding implementation is not possible so the project should still
compile with the standard C lib as a fallback. The project provides an
implementation of the stack protector in the freestanding environment
(using rdrand CPU operation when available and a static canary otherwise).
With all security features disabled the code section on x86_64 Linux is only
33 bytes and it looks exactly like something that you would get by compiling
a manually written assembly code :
4000b0: b8 01 00 00 00 mov $0x1,%eax
4000b5: bf 01 00 00 00 mov $0x1,%edi
4000ba: ba 0e 00 00 00 mov $0xe,%edx
4000bf: 48 8d 35 0b 00 00 00 lea 0xb(%rip),%rsi # 0x4000d1
4000c6: 0f 05 syscall
4000c8: b8 3c 00 00 00 mov $0x3c,%eax
4000cd: 31 ff xor %edi,%edi
4000cf: 0f 05 syscall
The ELF size is 512 bytes, which can be stripped down to 224 bytes by removing
all nonessential bytes from ELF using sstrip utility. (It potentially could be
reduced further by messing with the ELF program headers).