Fairly easily. My professional career has been mainly in maintenance and, as such, I get to see the gritty back-end of things, the end-result of all the technical-debt... and being more correctness and security-minded than most, I often note how a good design could help prevent problems, both on the language being used to implement and on the project itself.
From that perspective, most defenses of C as productive or useful fall flat on their faces, especially in recent years as security becomes more and more important a concern — about the only place that C makes any sense anymore is micro-controllers because "all the micro-controllers have a C compiler." — But let's not make the mistake thinking that "having a C compiler" means that C is a good (or even appropriate) language for the task.
Forth, Assembly, and Ada all exceed C's capabilities in many of what have been traditionally claimed as C's strong suits: * Ada: much, MUCH, better as a systems-language. Any project of medium or large size should seriously consider Ada instead of C. * Assembly: very fine control, especially important for the severely-constrained controllers. * Forth: Very fast, very low-level; would recommend for small/medium-small projects on small controllers. (Doesn't have calling-conventions, doesn't manipulate stack-frames; this makes it faster than C.)
> Look at the amount of C code in any Linux distribution, even ignoring the kernel itself.
And? That says NOTHING about having value in a professional setting, only that it (a) was chosen by a project that got big, and (b) enjoys popularity.
Nearly EVERYTHING that C is claimed to be [very] good about is done much better by some other language. C is particularly bad at large-systems, given the complete lack of modules, and entirely unsuitable for many things that it's commonly used for like multithreaded applications (honestly, take a look at Ada's TASK construct and consider how that might be used in [e.g.] a game-engine).
It is used inappropriately? Sometimes. Can there be better alternatives? Yes. But to say it has "no real value" is a bit extreme.
In the case of C, and other C-like languages, I most recently have four or five custom-made programs [some requiring specialized tools] that have little/nothing in the way of documentation: what it does, the "why-for"/motivation, any sort of high-level architecture-plan, or design-documents.
Fortunately for me, most of the programs actually do have documentation thanks to a true hero that left before I arrived.
The lower level C library was pretty clever. The original developer of most of it left about a year into my tenure there. He was one of the smartest guys I ever worked with.
The C++ "app" layer was a different story. The worst part it was a 3000 line switch/case block with about 100 different cases, chock full of copy-and-pasted code. It went on... and on... and on... I still have nightmares about it.
Ouch. That sounds brutal. If I had to do something similar, or maintain that, in Ada I'd leverage nested subprograms, local type/subtype definitions, and mandatory case-coverage — and I've done similar with VMs, particular opcodes — so you get something like:
Type Opcode is ( NOP, Add_A, SUB_A, ..., Rem_D );
Procedure Execute_Instruction( State : in out Machine_State; Instructions : in Instruction_Stream ) is
Subtype A_Series is Opcode range Add_A..Sub_A;
Subtype B_Series is Opcode range Add_B..Sub_B;
Subtype C_Series is Opcode range Add_C..Sub_C;
Subtype D_Series is Opcode range Add_D..Sub_D;
Procedure Do_Add_A;
-- other subprograms.
Current : Opcode renames Decode( Next_Token( Instructions ) );
--...
Begin
Case Current is
when A_Series =>
case A_Series'(Current) is
when Add_A => Do_Add_A;
end case;
-- other series.
end case;
End Execute_Instruction;
Of course you could structure it so that all the Do_OPCODE subprograms are local to the top-level switch, or local to the nested switches, as best suits the design; or decompose along 'families' of operation (Add_A, Add_B, Add_C, Add_D), but the important thing there is keeping things local/nested for maintainability.