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?
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.