
Object-oriented techniques in C - sea6ear
http://dmitryfrank.com/articles/oop_in_c
======
openasocket
This seems to just seems to be replicating the functionality of C++ in C. The
only reason he gives for not using C++ is lack of C++ compilers for embedded
CPUs. I think a better approach for the long term would be to use a C++ -> C
transpiler (as much as I hate that word).

~~~
pslam
I'm saddened by the author going to this trouble. Embedded cpu selection
doesn't happen in isolation - or at least, it shouldn't. The available tools
are a large part of why you would pick chip A over chip B. If you don't have a
C++ compiler, and you end up having to re-invent C++ in C, then maybe you
should have picked the chip with decent tools.

If this project really did require re-inventing C++ in C, it must be justified
by being fairly large. In which case, a low-end ARM (Cortex M based)
microcontroller would have been entirely suitable. All ARMs have multiple C++
toolchains which support them.

If this is a microcontroller you end up lumbered with, and didn't choose, then
fair enough, but this effort should be labeled for the hack that it is. This
is not best or even good practice.

~~~
vonmoltke
> If you don't have a C++ compiler, and you end up having to re-invent C++ in
> C, then maybe you should have picked the chip with decent tools.

So the software engineers are the only ones on the project who get a say as to
what chips get used?

~~~
azdle
No (and that isn't what he said), but surely they should get a say, right?

~~~
vonmoltke
pslam basically did in his next paragraph:

> If this project really did require re-inventing C++ in C, it must be
> justified by being fairly large. In which case, a low-end ARM (Cortex M
> based) microcontroller would have been entirely suitable. All ARMs have
> multiple C++ toolchains which support them.

ARM is _not_ entirely suitable for any application just because you have
complex (for some definition of complex) software.

~~~
pslam
No, but the software impact of choosing a particular piece of hardware should
absolutely be part of the system design process. If it is not, then you are
part of a project with bad leadership, or very, very old-fashioned architects.

It's not just ARM - there's plenty of other CPU architectures with modern
Clang, GCC, or other toolchains with C++ support. Given that this is a large
code base (justifying this amount of work), this cannot be a "tiny"
microcontroller, as in 8 bit, and needing to run on microamps. I cannot
believe that there were not C++-capable microcontrollers available which would
have done the job, without breaking the bank.

~~~
z92
Doing it at lowest cost is the point. Because even if you don't lower your
cost, your competitors will.

------
kbenson
Isn't there some way to emit C from C++? Wouldn't a capability like that and
using C as a host language completely bypass the need for emulating OOP in C,
by just letting you write C++? That seems like it would be far more
manageable, and since it's compiled, not susceptible to some of the major
downsides commonly associated with the technique (in JS).

~~~
meson2k
The emitted code is highly name mangled - one of the reasons why you
encapsulate the C code in "extern C"
[https://en.wikipedia.org/wiki/Name_mangling](https://en.wikipedia.org/wiki/Name_mangling)

~~~
kbenson
That seems like a problem that has quite a few possible solutions:
deterministic name mangling, hinting in the source as to how the name should
be mangled, an external mapping of mangling exceptions and/or rules to be used
by the transpiler. None of those are mutually exclusive, all could be used
together.

~~~
nostrademons
It's more than that - there's extra information that's compiled alongside your
runtime data structures whenever you write C++. For example, every class with
virtual member functions has a vtable, and every object of such a class has a
pointer to the vtable. Every time you access a virtual member function, it's
indirecting through the vtable to find the particular address to call, and
then calling it with the object itself as the first argument.

If you got rid of name mangling (and a few other C++ features that are besides
the point), you could certainly call C++ from C. The thing is - your C calls
would look exactly like what the article is suggesting. That's why it's
important to learn this technique: it is what your C++ compiler is doing under
the hood. Indeed, the very first C++ compilers were just preprocessors that
transformed C++ syntax into the type of vtable + base class + first parameter
indirection that you see here.

~~~
kbenson
Yes, so while this technique would be harder to adopt for a library, or at
least harder on the users, for an application where you don't really need to
worry about people using C calling into your code, it would be fairly useful.
A short HOWTO on how to call from C using the necessarily included and
emulated C++ vtable bits would make calling in from C possible and easier
where required, but you could still reap the benefits of non-C features while
sticking with a C toolchain at the lowest level.

~~~
nostrademons
Usually an 'extern "C" { .... }' declaration is easier if you control the C++
library code.

~~~
kbenson
I see what's being talked about now. I misinterpreted meson2k's original
reply, so haven't really been on the same page as you or him.

If _current_ C++ to C transpilers don't handle naming well, that's a problem
that should be worked on.

------
nqzero
this approach is similar to the one that GObject uses, though stripped down a
bit for embedded. i haven't used it in years, but it worked great with GTK+.
the only downside i remember is the overhead of the virtual function calls (in
contrast, the jvm is able to factor out the virtual call in many cases at
runtime)

one advantage of using something like this is it forces you to really think
about what makes something OO and gives you a deeper appreciation of it. i'm a
java programmer now, but to this day i still prefer object oriented C to C++

------
rbalsdon
I've found that, in the embedded world at least, the overhead this adds (in
terms of flash/rom and ram) can be fatal. That said, there are some more
implementations of this here if anyone is curious:
[https://github.com/ryanbalsdon/libr](https://github.com/ryanbalsdon/libr)

~~~
deathanatos
> the overhead this adds (in terms of flash/rom and ram) can be fatal.

I know you list the resources, but how does this approach add overhead? (how
does it use those resources more than a "C" approach would?) Most of the
constructs in C++ only cost anything if you use them, so you only pay if you
need the feature; further, the runtime costs of most C++ features are pretty
much exactly what's required for that feature… so if you were writing C, and
you needed the same functionality, how would you avoid paying the same cost?

------
bluedino
EFnet's infamous Zhivago (from #c) has a great article about OOP in C -
[http://www6.uniovi.es/cscene/CS1/CS1-02.html](http://www6.uniovi.es/cscene/CS1/CS1-02.html)

------
MichaelGG
How badly does this impact optimizations? First you add an extra function
pointer for any member, even if it's not virtual. Then since you're always
calling via a function pointer -- do compilers notice when fps are assigned
and never modified and do inlining and so on?

Also the first technique doesn't feel like OO in any meaningful way. Defining
a state object and passing it to functions is exactly what you'd do in say, a
functional programming language, or even in regular old imperative code.

~~~
dimonomid
> _First you add an extra function pointer for any member, even if it 's not
> virtual_

Wrong. If some function is not virtual, there's no point to add it to vtable;
so, it's just a regular function.

> _Also the first technique doesn 't feel like OO in any meaningful way._

Really? Of course I can be wrong, but for me, defining a state object and
operate on it only through methods (i.e. functions that are given a pointer to
state object) is exactly what is called an OO style.

~~~
MichaelGG
Thanks for the correction. I had misread and thought all functions were called
with member syntax.

I guess in simple cases like this the difference between OO and other styles
are pointless. I'm just thinking that if I were to write it in a functional
language, it'd probably a very similar API, though possibly/probably returning
a new state vs modifying.

------
kasajian
Also check out: Simply Object-Oriented C -->
[http://www.codeproject.com/Articles/22139/Simply-Object-
Orie...](http://www.codeproject.com/Articles/22139/Simply-Object-Oriented-C)

------
halayli
Unmaintainable and extra complexity just to emulate a paradigm that C wasn't
designed for.

~~~
dimonomid
Again, if we use it correctly, the resulting thing is much more maintainable
than the code with switch-cases instead of virtual methods, and so on.

By the way, similar approach is used across the Linux Kernel: check, for
example, the book "Linux Kernel Development" by Robert Love, especially the
discussion on the virtual filesystem.

~~~
halayli
Every solution feels great and neat in the beginning until you wake up one day
and feel sick to your stomach from the mess it has created.

Most vtable like implementations in the kernel are nothing more than struct of
function pointers, and this is almost exclusively.

~~~
dimonomid
As nostrademons pointed out above
([https://news.ycombinator.com/item?id=10261375](https://news.ycombinator.com/item?id=10261375)),
if you feel that some tool you've used has created mess (so that you even feel
sick to your stomach), it's most likely not because the tool is bad, but
because you've used it in inappropriate way.

------
vaskebjorn
"The danger in trying to force object-oriented concepts onto a C base is to
get an inconsistent construction, impairing the software development process
and the quality of the resulting products. A hybrid approach yields hybrid
quality. This is why serious reservations may be voiced about the object-
oriented extensions of C described in chapter 20. To benefit from object-
oriented techniques, one must use a consistent framework, and overcome the
limitations of languages such as Fortran or C which regardless of their other
characteristics -- were designed for entirely different purposes." Bertrand
Meyer, Object-oriented Software Construction, 1988.

------
giagab
This interesting article reminded me of this post by Rob Pike from some time
ago:
[https://plus.google.com/+RobPikeTheHuman/posts/hoJdanihKwb](https://plus.google.com/+RobPikeTheHuman/posts/hoJdanihKwb).

While I've been a C++ programmer since my uni days, I think algorithms and
data structures should come first, and I don't like the idea of "bending"
programs to make them fit nicely within the OO paradigm.

------
TickleSteve
Do _NOT_ use these techniques please!

I have experience of an early nineties project that was entirely structured
like this and trust me, it ends up as a disaster.

C is not meant to be OO, consequently all the tooling in editors and the like
do not understand the links between classes and methods.

What it ends up like is an impenetrable mess with all the mechanics of C++
exposed but being impossible to navigate.

again... please do _not_ do this in any project... for the sake of any who
will follow you in maintaining your code!

~~~
nostrademons
Bah. It's a tool. Just like any tool, you should have it in your toolbox, and
use it when appropriate.

When I hear about C programs that were a disaster because they used OO-like
techniques, the operative word is usually "disaster" and not "C" or "OO". In
other words, there were other things wrong with the project that had nothing
to do with the selection of language or programming paradigm, like hiring
inexperienced programmers or mandating a single programming paradigm for the
whole codebase.

Good C code will have a lot fewer objects than say, Java, because most of the
things you want to use C for, you can accomplish just fine with structs and
functions. But you shouldn't shy away from a struct full of function pointers
or an ADT with opaque pointer types and appropriately-namespaced member
functions just because you're afraid a maintenance programmer won't get it.
It's their responsibility to learn the language.

~~~
chipsy
Seconding the idea that you don't use something like this prolifically. The
point is to only go slightly past the limits of the tooling and still
primarily be writing C in the style that C is best at.

For similar reasons, "cute" macro-heavy C code tends not to survive long-term,
because it breaks too much tooling.

------
pmalynin
A part a of series of articles on HN: How to %feature_from_c++% in C (when you
could have used C++)

~~~
meson2k
or when memory constraints prevent you from using libstdc++

~~~
jevinskie
You can get C++ OO without linking in a whole STL. IIRC it is the C++
equivalent of -ffreestanding in C. Given that popular toolchains like GCC and
IAR offer C++ support to micro controllers as small as ATtiny 8-bit AVR
micros, I don't see the necessity to reimplement C++ in a hamstrung C form.

I'm currently writing a binary parser library using C++ but without the STL.
I'm able to use range-based for loops, zero-cost iterators, and other features
without any dependencies on a C++ STL library. After optimization, the
generated code is essentially what the C equivalent with all the boilerplate
would compile down to. There are plenty of OS kernels written in C++, you just
have to pick the appropriate C++ subset. ;-)

Regardless, I enjoy the stricter typing in C++ that generates essentially same
code as C with OO tacked on but with less boilerplate.

------
swah
I don't feel like the crc32 example is a good one. Maybe the game entities
example would have been better...

~~~
deathanatos
IIRC, MD5 (and probably the other hashs) are implemented in exactly this form
in OpenSSL.

I can't really think of a more straightforward way to implement CRC32,
frankly, especially assuming that maybe you don't want to force the entire
buffer to be present. If you did, you could `uint32_t crc32(unsigned char *
buffer, size_t len);` — but you can implement that function in terms of the
interface in the article, and if you pick up a bit of inlining from an
optimizer, I think it would end up being just as efficient, too. (If you
don't, you'll end up with some extra pointer dereferences, but again, the
streaming interface supports streaming.)

And perhaps to stress what some other commenters are missing: it's not "you
should always OO in C"; it's that _if_ you want an interface to compute, while
streaming, the CRC32 of a stream — you need to store that state somewhere, and
you need to act on that state somehow. That state is an object: it has a
setup, and potentially a teardown, and two relevant methods: feed it data, and
get the computed hash.

~~~
exDM69
CRCs or MD5 or other hash sums don't require inheritance or other OO tricks or
even constructors or destructors (apart from zero-initialization). The state
is just a single integer. For other hashes, the state might be slightly more
complicated but there's still no need for fancy OO.

If you want to build a system where you can change the hash function at
runtime, you might need some kind of interface or inheritance. But if you end
up needing a virtual call per byte (or per a small chunk of bytes), you're
killing your performance.

And besides, there's so few hash functions you end up needing in practice that
a simple switch-case would do the same with less boilerplate and probably give
better performance because compiler optimizations can take place (virtual
calls tend to prevent many optimization tricks).

I agree with GP... The choice of CRC was a poor example for an article about
OO in C. Something related to filesystems or device drivers would be vastly
more practical.

~~~
deathanatos
> _CRCs or MD5 or other hash sums don 't require inheritance or other OO
> tricks_

Given some of the other comments, I think this ought to be stressed though… OO
doesn't strictly require inheritance, or vtables. A fair number of classes in
the C++ STL don't use those features, for example.

It's not that the state is a single integer, either, it's that the concept of
a state exists; having a separate type (with the functions surrounding that
separate type) exist to embody that concept, and can change a function from an
ambiguous "what goes in this uint32_t arg?" to a very obvious md5_state_t.

> _or even constructors or destructors (apart from zero-initialization)._

Just a nit: MD5 requires initialization that is more than just zero-
initialization.

> _probably give better performance because compiler optimizations can take
> place (virtual calls tend to prevent many optimization tricks)._

This is about the most concrete argument against vtables (but hardly against
OO in general) thus far, and I'd still love more explanation: what makes a
branch more optimizable than following a pointer?

------
bliti
This is probably a stupid question, but here it goes:

Embedded platforms tend to have size limits. Would this increase the size of
the source code? Seems a bit verbose to me.

~~~
dimonomid
Surely it does have some overhead. And, by the way, passing arguments to
function has an overhead as well, comparing to globals. But, how often would
you recommend use globals?

The more interesting question is: how much overhead? And the answer is: it
depends on the MCU. For example, some 8-bit PICs don't have silicon support
for indirect function call (i.e. by function pointer), so they work around
this problem by saving function address to the stack and execution 'return'
instruction. It works much slower and code size increases notably. I don't use
these techniques on these chips.

But for 16- and 32-bit MCUs that I was working with, it works flawlessly. For
most of our projects, the overhead is much less significant than the
maintainability we get with this approach. As I said in another comment,
engineering is all about tradeoffs.

~~~
deathanatos
> _don 't have silicon support for indirect function call (i.e. by function
> pointer), so they work around this problem by saving function address to the
> stack and execution 'return' instruction._

While no doubt this is true, if you have an indirect function call in C++ OO
code, how do you _not_ have it in C? That you had it in C++ implies — I hope —
that you needed it. The C code can't simply whisk that need away. (Or, if it
can, so can the C++…)

------
mpweiher
Hmm...those who don't understand Objective-C are doomed to reinvent it, badly?
(Yes, have used OO C techniques of different sorts and implemented ObjC
compiler/runtime).

~~~
Apocryphon
Isn't this article's implementation of OO C based on C++?

~~~
mpweiher
"...badly" ;-)

~~~
jjoonathan
Method hashes (ObjC) != Vtables (C++). The former allows runtime introspection
and modification that the latter doesn't, and the latter allows performance
that the former doesn't.

