C makes it difficult to write generic code. Unlike C++, which gives you templates and virtual functions, C only has 3 mechanisms for writing generic code:
void*
pointers
- Preprocessor macros
- Function pointers
void*
pointers are far from ideal, since you lose all type safety provided by the compiler, which can result in hard-to-debug undefined behavior resulting from invalid type casts.
Preprocessor macros have well-noted drawbacks - preprocessor expansion is basically just a find/replace mechanism that happens before the compilation phase, which again can result in hard-to-debug errors. The archetypal example being something like: #define add(x) (x+x)
, where x
can be incremented twice if you call add(i++)
. You can write template-style generic code entirely using C-macros, but the result is really hideous and difficult to maintain.
Function pointers provide a good way to write generic code, but unfortunately they don't provide you with type generality - they merely provide the possibility of run-time polymorphism (which is why, for example, the standard library qsort
still requires a function that takes void*
pointers.)
You can also implement class hierarchies in C using structs, as is done in the GLib library which provides a generic GObject
base class. But this suffers from similar problems as using void*
pointers, since you still need to rely on potentially unsafe manual casting to up-cast and down-cast.
So yeah, C makes it hard to write code that is both generic AND safe/easy-to-maintain, which unfortunately can result in code duplication. Large C projects often use scripting languages to generate repetitive code during the build process.