Except C is quite a poor example for how to use modules.
TU with opaque pointers can be used for such purposes, and they are, but still fail short of Modula-2, Mesa, CLU and UCSD Pascal, to cite contemporary examples.
Modules either have types as part of their interface, or haven't. I C, the declarations shipped in the interface header represent the interface, and the types declared in it are public types.
C also supports symbols with internal linkage.
What exactly is missing?
> module initialization/shutdown,
What's wrong with initializing objects? Languages such as Rust also rely on factory methods to instantiate objects.
> linking with type checking,
The interface headers define the symbols, which must correspond to the symbols handled by the linker. What's missing?
> namespaces.
That definitely C doesn't support as a first class citizen, but this is nothing that symbol prefixes don't handle.
How come? C makes it trivial to create self-contained modules.