I've been dabbling in embedded programming. Everything is written in C. I just don't understand why. C++ solves pretty much all problems if you want it too (RAII, smart pointers, move semantics) and the frameworks writers wouldn't need to implement their bespoke OOP system on top of opaque pointers and callbacks.<p>Maybe it was bad luck on my part, and other embedded frameworks are better; but I got into both ESP32 and STM32, both frameworks are the worst spaghetti code I have ever seen. You need to jump through at least one, often two layers of indirection to understand what a particular function call will do. Here's an example of what I mean:<p><pre><code> // peripheral_conf.h
#define USE_FOOBAR_PERIPHERAL 1
// obj_t.h
#define USE_OBJ_PARAM2
// In the library header
#ifdef USE_FOOBAR_PERIPHERAL
#define DoSomethingCallback FoobarCallback
#endif
// foobar.h
status_t FoobarCallback(int32_t data, int32_t param);
// obj_t.c
status_t Init(Obj_t* obj) {
obj->param1 = obj->init.initparam & 0xFF;
#ifdef USE_OBJ_PARAM2
obj->param2 = (obj->init.initparam >> 16) & 0xFF;
#endif
obj->callback = DoSomethingCallback;
return OK;
}
status_t DoSomething(Obj_t *obj, int32_t data) {
#ifdef USE_OBJ_PARAM2
return obj->callback(data, obj->param2);
#else
return obj->callback(data, obj->param1);
#endif
}
// main.c
Obj obj = {0};
obj.init.initparam = 0x12345678;
Init(obj);
DoSomething(obj, 0x42);
</code></pre>
And that's an <i>easy</i> example. Macros everywhere, you need to grok what's happening in four different files to understand what the hell a single function call will <i>actually</i> do. Sure, the code is super efficient, because once it's compiled all the extraneous information is pre-processed away if you don't use such and such peripheral or configuration option. But all this could be replaced by an abstract class, perhaps some templates... And if you disable stuff you may not need (RTTI, exceptions) then you'd get just as efficient compiled code. It would be much easier to understand what going on, and <i>you wouldn't be able call DoSomething on uninitialized data</i>... Because you'd have to call the constructor first to even have access to the method.<p>Anyway, thank god for debuggers, step-by-step execution, and IDEs.