At least 5 times, probably more, I have seen C code compared to CeePlusPlus or some other language as an alleged justification of ObjectOrientedProgramming.
The CeeLanguage is not a very powerful representative of procedural in my opinion. Its claim to fame is CPU-based speed and portability, not its software engineering, conciseness, and developer productivity. So I beg, please stop using C to justify OOP.
[Lisp flame-bait removed and tossed into the ObjectiveAdvantagesOfLisp garbage can.]
Lisp may serve as a possible reference alternative to C, flaming aside.
So what is the pinnacle of procedural? Pascal? COBOL? How do we measure this?
Algol?
You can write purely procedural programs with RubyLanguage. I'd say that's the pinnacle, unless you have a problem with the OO-ness of the core library.
Ruby is a pure object oriented language, according to their website and propaganda. Therefore you can't do purely procedural programming in Ruby. Or can you, and maybe they lied? Well, when one writes so called "procedural" code in Ruby, it's not really procedural according to a RubyWeenie?; it's just a global object. (However, in my own opinion, and in Wirth's opinion, and many others' opinion, object oriented programming is procedural programming, but with more extensions and abilities added to structs/records.)
It's SchemeLanguage. What is? Surely not the pinnacle of anything.
An example of where C's lack of features may make OO look better is in adding new parameters, especially lots of optional parameters. In C++ you set only the attributes you need, and the rest default to the parent. If C had optional named parameters then you could more easily get similar behavior. Or, if it at least had a built-in associative array to put parameters/settings into.
Things about C that make programming harder or more bloated:
- Lack of optional, defaultable named parameters
- Lack of nested routines (Pascal style)
- Lack of native string concatenation
- Lack of native associative arrays. (Languages that use dot syntax for associative arrays make for very compact syntax. And they allow square braces for times where odd characters are used as the key.)
- Ugly typo-prone case/switch syntax (IsBreakStatementArchaic)
- Lack of dynamic code execution (like Eval() or Execute())
- Lack of dynamic module look-up (for missing names)
- Over-reliance on pointers
- Lousy support for tagged unions (Or is my OCaml showing through?)
C++ gives you more choices to compensate for these holes. That does not mean that OO is better overall, just that C++ gives you more syntactical options to work around the weaknesses. 12 bad tools are better than 5 bad tools.
Or are they 2.4 times worse? {Usually they are better than doing it by hand or with rocks, but not much more. For example, a stripped screwdriver may still be useful once the screw is mostly loosened already.}
Ideally, though, you want a select group of well-chosen orthogonal tools to keep your toolbox simple and clean but flexible. The C/C++ family does not provide that in my opinion. The C/C++ family's claim to fame is machine portability and machine speed. It serves the machine, not software engineers. Some have called C "CPU-neutral assembler".
Re: "Things about C that make programming harder or more bloated"
- Optional/named parameters likely need code support at run-time. C has no a run-time like Objective-C or other compiled language. By choice, I believe.
- Only optional parameters need any more run time support than the normal (for C) positional parameters. Named parameters could be handled by reordering at compile time. Default parameters can be handled at compile time (see C++'s default parameters). It's only optional parameters that actually need additional run-time support. The called function would need some way to tell which optional parameters were actually used. By the way, C already has support for a limited form of optional parameters.
- GNU C supports nested routines as extension... will they be part of a future standard? Not a bad idea
- Strings are "objects" more complex than one may think at first; C has no strings, as microprocessors has no strings. Libraries to handle efficiently and easily strings exist; the rest is syntactic sugar. An option could be using C++'s STL string, without using other OO features of C++.
- Technically, the statements "C has no strings", and "C lacks native string concatenation" are both false. What C lacks (and this would be my guess as to the actual issue) is a variable length string object that manages its own memory and can be manipulated as a string. Currently, C's strings are fixed length objects that have to be manipulated as arrays.
- Regardless, concatenation (such as ".", "&", or overloaded "+" in other langs) is not syntactically supported. Using libraries is more verbose.
- Associative arrays are other complex "objects". Even in this case, libraries exist, and the rest is syntactic sugar. Bare C has no such complex objects; again, by design/choice, according to me.
- Syntactic sugar shouldn't be so easily dismissed. It can make code more compact and easier to read, maintain, and type.
- Ugliness is a subjective matter, not a technical point or missing feature!
- I assume you are referring to case/switch syntax? C's is not just ugly and more code, it's also error-prone because one has to remember the "break" statement and one cannot tell if a missing one is intentional. Perhaps it is subjective, but WetWare matters. Code is for humans as much as it is for computers. If you want to defend C's case statement over alternatives, I'd welcome a write-up. Most who have used better ones agree it sucks (although some argue it's faster). C's has a goto-like flavor, while newer ones are block-friendly and influenced by SetTheory to do the same thing.
- Dynamic code execution is good for interpreted / JIT compiled languages. C gets compiled at time A and then you can run it at time B, C, D...; there's no a C compiler inside the standard library; it could be embedded, maybe; anyway this is true for every "compiled" language (FortranLanguage, AdaLanguage, PascalLanguage and so on), so the topic is "compiled against _interpreted_"
- I am not sure to get this point exactly, however it seems another feature that fits the interpreted language domain. Libraries can be loaded at runtime anyway (e.g. with dlopen) and pointers to function retrieved with dlsym.
- Strictly speaking, things like dlopen and dlsym are not part of the C language. This is both a strength and a weakness. C is used in environments where those things simply do not make sense, and requiring support would, at best, be a waste of time.
- Pointers do not hurt if you know how to handle them... most of the time you can "hide" them, and consider the "*" and "&" just as a syntactic way to say if something is given by reference or by value...
- I don't know anything about tagged unions, but I suppose a lot of languages haven't them, so C can survive without them...
- Implementing tagged unions in C isn't difficult (and C already uses a number of tagged unions where the tag has been folded back into the value), so if you want them, you can have them. What you don't get are the compile time checks that some languages provide.
I believe C has all a "mid-level" language must have, and few more things. If software engineers need "higher level" tools, then other languages, as already said, exist. C is good at what it is good, and I think it will survive to all these "critics".
Because it is machine-friendly, and for some uses, such as SystemsSoftware, that overrides being human-friendly. Nobody is arguing it has no place or that it should die, only that it's a poor example of what a procedural language can be in terms of the expressiveness side of software engineering.
CategoryCee