In the case of leaking, any. You have to be a retard to leak memory in C. A free for each malloc isn't rocket science. Bad design and ignorance are the most common causes for memory leaks. You either create a mess so complicated that you can't figure where to free your memory or just don't know that you are supposed to free it. Objective-C and especially C++ have more problems because of the whole instance creation and destruction abstraction, but in the end the problem is ignorance from ex-Java people that were used to create objects and let the Garbage collector pick them up.
For complex data structures, one free for every malloc can indeed get very tricky to handle. For example, in C++, one can just write a destructor for a tree node that calls delete on it's children before delete'ing itself. Free'ing all memory is as simple as calling delete on the root node. Writing a recursive deallocator in C is trickier to get correct.
There's also issues of pointer ownership, problems that C++ and Java also inherit. Sure, this can be avoided if you have very clear cut guidelines on who owns what, but even these will fall short on some cases and those exceptions will need to be treated very carefully. The Python C interface has such guidelines.
My point is, you can be very intelligent and still manage to leak memory in C. It's completely doable, and as the complexity of your code increases, so do the chances of memory leaks. C++ and Java abstractions go a ways to help, but aren't enough by a long shot. This is why tools like valgrind are still relevant.