C declarations are hardest to understand when they involve (1) "arrays"† of "arrays" of "arrays", (2) function pointers, and (3) multiple layers of indirection.
In systems and networking code and in most application code, (1) "multi-level arrays" are uncommon, and they're uncommon in roughly the same way tuples of tuples of tuples are uncommon in Python: they're a symptom that you're missing a level of abstraction.
Function pointer declarations (2) are common and annoying to read. However, in virtually all cases, the core of what you're trying to express with a function pointer declaration is "what arguments does this function take" and "what does it return". More importantly, most idiomatic C code typedefs function pointers, so you're not looking at prototype decls with nested complex function pointer decls inside of them.
So instead of
void (*signal(int sig, void (*func)(int)))(int);
a modern C programmer would expect typedef void (*sig_t) (int);
sig_t signal(int sig, sig_t func);
The qsort(3) man page is another example of an archaic declaration (its authors presumably think this is the clearest way to convey qsort to an experienced programmer); check out the man page for pcap_loop(3) for a better example.In most C code, multiple layers of indirection (3) are one-step pointers-to-pointers used to work around call-by-value in C. If you store in your head the notion that a pointer to a pointer is just there to provide a return-value argument, that's probably all you'd need to remember about it. Pointers to pointers to pointers are rare.
† C pedants: I'm using "array" in an intentionally vague sense.