Students seem to be unable to use code to express their ideas. Is the teaching methodology improvable?
There is a lot to unpack here, but first, YES, the teaching methodology can be improved, but that is always true, an unending quest. 

Next, I'll note that C is a language with enough pitfalls that many people struggle with it at the detail level, finding it harder to think more globally. My preference, perhaps not open to you, is to start with a language at a higher level of abstraction generally and then, later in the curriculum, work both downward toward the machine and upward from where you start. An OO or pure functional language, if well taught, makes abstraction easier. 

However, the biggest recommendation I'd make to address the problems you state is to introduce (at the beginning) unit testing and test driven development (TDD). Find a good testing environment for your language and insist on its use: No Code Without a Failing Test. 

I normally start out a course with TDD, combined with giving assignments in an already logically decomposed way. Instead of a large program to write (large is relative, of course), I give them the "top down" decomposition initially that, in C they would be able to implement by writing a lot of functions. 

Thus, at the beginning, I relieve them of having to face a blank screen with a large problem and nothing but 'main()' to work with. Later you can continue this, but making the pieces bigger, requiring some decomposition (helper functions) be developed. My mantra for a "too big function" is about five lines. My mantra for "too complex function" is nesting more than two levels. You can break these rules for especially boring code, but not for the interesting logic of a program, or it will be impossible to understand and maintain. 

Note that breaking a problem up into parts is how Agile Development (Extreme Programming, Scrum, ...) actually work, with the Customer (in this case you) giving the Team (your students) manageable problems each of which contributes value. 

Also, with TDD, you can provide some tests initially along with simple requirements. Teach them to stop programming when the tests pass. Or, write additional tests and make them pass. But the tests are small and simple, usually without internal logic, and the application code needed to make them pass should also be simple function and uses of functions.