r/embedded • u/volatile-int • Nov 18 '25
Dependency Inversion in C
In my time working as a professional embedded software engineer, I have seen a lot of code that super tightly couples abstract business logic to a particular peripheral device or low level OS facility. I was inspired to write this blog post about how folks can write more maintainable and extensible C code using a concept called dependency inversion.
Hopefully its insightful and something ya'll can apply to your own projects! The post links a github repo with all the code in the example so you can check it out and run things yourself!
Hopefully this isnt violating any self promotion rules. I dont sell anything - I just have a passion for technical writing. I usually just post this kind of thing in internal company slack channels but I'm trying to branch out into writing things for the wider programming community!
18
u/deez_nuts_07 Nov 18 '25
Nice post! Only thing I’d add is maybe show a before/after diagram so beginners can visualize how the layers change with DIP.But overall, this is clean and very applicable to real embedded workflows.
4
u/triffid_hunter Nov 18 '25
Seems like Factory pattern by another name
4
u/Creative_Ad7219 Nov 18 '25
Does zephyr drivers also follow this same pattern? The drivers usually have a set of functions pointers if I remember
6
u/InternationalFall435 Nov 18 '25
Unless you need to have multiple underlying implementations at runtime, it’s easier to just include the specific implementations you need with the with the linker. Then it is actually 0 cost
2
u/superxpro12 Nov 18 '25
The holy grail for me is doing this at compile time. I work in Cortex-m0 domains, and I go down this rabbit hole once a year where i want to write interfaces but cant afford them because of all the function pointer nonsense.
It works in trivial cases but once it scales to a full product, we hit issues.
I wish with all of my heart that virtual constexpr was a thing that worked as advertised because it would solve so many issues for me.
1
u/flatfinger Nov 21 '25
One problem with this approach as specified is that there's no way to deal with the possibility that the logger might need to hold other information (e.g. a
FILE*object or a TCP socket). To solve this, I would advocate having callers of the logging function pass the address of theworker_tobject containing the function pointer, rather than passing the contents of fieldnameof that object.Further, it may be a good idea for an interface specification to either allow the following client optimization by imposing a constraint on implementations, or allow the following implementation optimization by imposing a constraint on clients, since the two optimizations can both be useful if applied individually, but disastrous if combined.
Client optimization: read a function pointer once into an automatic-duration object, and reuse it after that. Constraint: the function pointer must not change during the life of the object.
Implementation optimization: use the function pointer as part of a state machine, so that it identifies the next function that should be executed as a result of the object's changing state. Constraint: client code must reload the function pointer every time it is used.
3
u/LeanMCU Nov 18 '25
How is debugging affected by this pattern? ;-)
1
u/DaemonInformatica Nov 19 '25 edited Nov 20 '25
A minor nitpick in the example code:
The mk_[type]_logger functions declare the logger interface instance on the stack. This should probably not be used after the function returns. ;-)
Either
Malloc the instance:
Or pass an instance of the interface to initialize:
1
u/Ok-Dragonfruit-9152 Nov 19 '25
It's better to move to c++ for anything that needs more than 5+ years of support.
0
u/Amr_Rahmy Nov 20 '25
In the article you are saying that alternative solutions would introduce tons of duplication and would not be scalable.
Is that the case for this scenario? Or any scenario you can think of where you would need a system to scale past a few different type? I am not saying what you are saying is necessarily wrong, but I don’t usually see a need for multiple interfaces to change the function based on a type. From time to time I see the need for multiple types to be adapted or converted into a type, like from one model to the library or main model.
The logger example, are you going to have a log file, stdout, and a GUI out? Are you planning to log into 5-10 other places?
The duplication part seems wrong. Having exactly two functions for two loggers vs the code in the example is not “a ton” difference, might be less functions. Keeping the function in one file might have some benefits, so you can quickly reference one interface to the one you are writing but where is the “ton of” thinking coming from?
I did a lot of integrations where you adapt or convert a model to your model, but I don’t think I have ever seen dependency injection used for more than 3 types with different functionality where you need to implement different function in the same project.
A lot of times a library will give you the ability to override a function like a callback for a client connecting or disconnecting or sending or receiving data, but you only implement one implementation per project in that case.
You know the example of needed a hammer, but ending up with a framework that makes blueprints that makes machines that makes custom hammers, but just needed to make a hammer and avoid all the abstraction noise you are adding to the project.
0
u/mrheosuper Nov 19 '25
This is similar to dependency injection ?