Let me summarize the main points in the case against C:
- Since there is no structured way of creating a dynamic array, it is not possible to write clear code without resorting to messy memory allocation. The usual allocation functions such as malloc() and realloc() return an address of a block of memory that does not impose any structure on what is to be stored there in routines that use arrays. In particular, string handling (pointers to characters) is very arcane, verbose, and error prone. Malloc and realloc should be available for emergency rather than for general use.
One could argue that clean functions and wrappers could be created as a library for C programmers to use to allocate memory, strings, arrays, and other structures in a standard fashion (while leaving malloc and realloc for emergencies). This has not been done. It is year 2007, C has been around for a long time.
- The lack of more built in types such as an actual boolean (only introduced in C99 decades later, making old code buggy or incompatible), and standard method of string allocation and concatenation destroy the clarity and consistency of a program. Some may have different notions as to what TRUE should be defined as, -1, 1, 0, which makes tests against TRUE potentially dangerous and again programs are less consistent. C programmers reinvent the wheel each time than they ought to. Although the boolean is a minor type that C forgot to implement, it is an important one, and it shows how childish the language and the inventors are for leaving it out. One possible reason could be that drugs were involved.
- Assignments inside if logic is confusing and causes bugs in software. It is not difficult to use assignments outside of the if logic code line - yet C allows one to assign inside if logic anyway. This "feature" in C is a "bug". For example "if x=y" in C does not mean if x equals y, it means instead x is assigned to y, even though it reads "if x equals y".. the Equal sign is confusing. Assignment and equality are not clearly distinguished by the language since the mathematical equal sign no longer means equal in C.
- The pointer to char is flexible but extremely verbose since the code is obfuscated with memory allocations instead of algorithms that do something. Most of the coding in C is not doing anything useful, it's just allocating more memory (which could be automated away instead). Pointers to chars should be available emergency access instead of an essential piece of every single even trivial program. Even low level system code makes use of strings extensively, and bloating up code with memory allocations means C is a verbose rather than terse language. This verbosity (memory allocations scattered everywhere) does not improve the safety of the language - it actually is the culprit of many bugs in C programs.
To have a verbose language where the verbosity encourages bugs rather than tries to prevent them is beyond believable. Everyone can allocate memory for pointers to chars in different unstructured ways, making programs inconsistent. Inconsistency also leads to bugs or hard to debug code since each developer must relearn pointers to characters each time he reads someone else's program.
- Advocates argue that C is a low level language and that if one does not want to allocate memory they should use other languages instead. In fact C is used as a high level language to make all sorts of operating system programs - not just low level system code. In other words C programmers don't practice what they preach: they claim to use it for low level system code only, when in practice C is used for high level GUI programs and operating system programs of all sorts.
- The multi-pass nature of the language forces code to be compiled extremely slowly, especially in serious, large applications. The unenforced "declare anywhere" style scatters program components and encourages sloppy programming and also adds to the slow compilation.
- The lack of separate units, namespaces, and nesting encourages global spaghetti procedure naming (unless one uses static in front of private functions in separate files, which becomes redundant and verbose (instead of a static section or private section for example)). One could use structs to point to procedures to make them more modular, but is a messy and largely unused/unknown hack. These issues impede the development of large programs and makes modular scoping a pain. Overloading functions by accident with extended C compilers can occur also (almost zero people use an actual compliant strict C only compiler), since non-static functions may contain the same name in different files, and especially because of weak typing.
- The macro preprocessor is not even heavily discouraged, which leads to code that can be worse to debug than unstructured GOTO statements or assembly code. Instead of extending the obsolete language, the macro preprocessor is encouraged when a feature is requested.
- The "switch", "case", "break" statements are extremely verbose and redundant compared to even pathetic standard Pascal's simple case statements that don't require continual breaking or repeating the case keyword over and over again. [update 2016: GoLang partially fixes this problem, but you still have a lot of repetition of the same keyword, unlike even pathetic standard pascal which is simpler]
- The "for" loop declaration is unnecessarily complex and is more verbose (and more obfuscated at the same time) than even Pascal. Since the for loop is an extremely often used construct in programming, it makes sense to make for loops simple, leaving more complex iterations to the while loops. The { curly brace and the ++ and } and the ( ) required in the for loop syntax strains the fingers due to the amount of shift key reaches required.
Simple for loops become a tedious task to even type on the keyboard, discouraging prototyping (along with the tedious compilation steps and make file requirements). Yet the author of C claimed to choose the "=" assignment operator because it was easy on the hands since it was typed often??? Makes no sense. Curly braces are the same problem, in hard to reach areas with shift key requirement.
[update 2016: GoLang solves most of these problems]
- The language lacks most of the tools needed for assembling large maintainable programs, most notably reference counted strings (or garbage collected), and modules, and the ability to switch on a stronger typing. Instead, C encourages the use of weak types in all programs, encourages declaring pointers in virtually every single program for even trivial algorithms, encourages global namespace procedures sewn together with include files and no modularization.
The language encourages the use of excessive pointers and preprocessor macros to defeat the crippled language. What this means is that C encourages creating maintenance nightmares and verbose MemAlloc() spaghetti code when it is unnecessary, even on a 386 computer. Back in 1970 one could argue that C was needed during those times on that hardware. Wonderful future thinking.. i.e. future proof language we're sure, yes.
[Update 2016: GoLang fixes much of these problems]
- The goto labels are not even declared, meaning bad code cannot be easily spotted when scanning through code that needs to be maintained by multiple developers. The goto labels are hidden without being made obvious through declarations (int's require declarations - if labels don't require declarations, why int's? The language is brain damaged). By not requiring labels to be declared, it makes goto's easier to use, and one can utilize them without a second thought.
It is easy to miss goto labels (esp. on larger projects), since scanning through declarations will not reveal them. One must sort through the entire code to know if goto's are used. Declarations of labels also encourage one to comment the source as to why a label was chosen, instead of commenting near the actual goto algorithm. The lack of label declarations causes bugs to be harder to find and code to be sloppier since goto' are more hidden from the eye. A rarely used construct such as a goto should be made more obvious than other constructs, not less obvious.
- Due to the general declaration syntax in C, code is read in all different directions, right to left (backwards), assignments left to right, while other declarations are scanned inside out from the center. Code and declarations become unnecessarily confusing (hint: this isn't a complaint that the language is too complex to understand for toked geniuses, it is a hint that it doesn't have to be so).
- The pointer declaration syntax is even worse, and cannot even be described on paper in words.
- Blame the programmer. People don't make mistakes.
- There is no escape.
This last point is perhaps the most important. The language is inadequate but circumscribed, because there is no way to escape or workaround the above points without extending it or abusing the macro preprocessor. Its limitations force one to either extend the language beyond its standard, or write bug ridden code including over use of pointers and/or complex macro preprocessors. This leads to widespread use of non-standard sloppy preprocessor macros, and clever but ultimately dumbfounded pointer tricks. It also leads to several (not just one) non-standard implementations of C such as Objective C, C++, Digital Mars D, Java, Go Language, and many more.
C++ compilers now compile both C and C++ code encouraging even sloppier conformity to standards, making C just as bad as Standard Pascal from a standard point. GNU C compilers and others add all sorts of extensions to C such as something similar to pascal's functions inside functions (sub procedures or local scope procedures).
The language is an oxymoron. The C language is too simple and too advanced. With the extensive pointers that C encourages, it is too advanced to do anything simple. The language near forces one to use pointers for even trivial programs. The language encourages one to use the advanced preprocessor due to the limitations of the simple language. The language is an old sword with pointer edges, not a gun - therefore one can not even shoot himself in the foot in C, one can only stab his foot, slowly torturing himself, finding bugs devouring leaks of red later rather than sooner.
Myth: C is Standard
There is no current standard compliant C compiler that significant amounts use (C99 is not implemented even in the most popular compiler in the world MSVC). It is hard to tell if any compiler even supports C99 at the date of writing.
The C compilers that claim to be partially compliant generally support C++, and C++ encourages complex unmaintainable code (operator overloaders, abstractions, and templates along with the preprocessor make even more unmaintainable code than in C). C++ has not even a string type, one must use a class and overloaders (and still plenty of pointers) for even trivial programs.
The important point to note is that there is no standard C that masses use today, just as there is no standard Pascal that masses use today - meaning that C is basically in the same state as Standard Pascal. It must be extended. In fact, it already has been extended, too many times, in too many forms. PHP is basically C for web... but it's not standard C because PHP is not C.
This was the exact same criticism made about standard Pascal.. that the language would be extended over and over again, or that people would make languages to add to Pascal. With C, you've got PHP, Ruby, Python, Perl all originally created in C because C wasn't good enough for the job... It just requires looking in the mirror folks, C suffers similar problems to standard pascal "extensions". Those critical readers will argue that no, Python and PHP are different languages they are not extended versions of C... well guess what... you missed the point. Take a look over at C++, what do we have here folks. Oh, Objective C you said? Nice to meet you, extended C.
Digital Mars D may solve some issues in C++ and C, except for the arcane syntax, verbose obfuscated for loops, and other griefs. However, discussing the difficulties and issues of C++/D and future non-standard C languages is beyond the scope of this article.
Myth: the language is terse and elegant
Large tab spacings are often used and even encouraged in the style recommendations to try and compensate for the arcane and obfuscated syntax, forcing the code to wiggle and jump around the screen with huge whitespace areas. The code even strains the eyes after short sessions. The amount of whitespace used causes whitespace verbosity, rather than keyword verbosity - which in the end has the same or worse effect as begin/end pairs in standard Pascal. The amount of symbols in for loops and other heavily used constructs increases finger stress on the pinky fingers (which some have been hospitalized for, no exaggeration), which has the same or worse effect as typing out long reserved words.
The for loop (which have been inherited by Java and PHP and C++, sadly) is more verbose than the for loop in Pascal (complex for loops shall remain in while loops, since complex loops are less often used and for loops are very often used). As already mentioned, the case/switch/break statements are also unnecessarily redundant and verbose. Many of the features in C are not examples of terse obfuscation, but rather examples of verbose obfuscation. Terseness does not equal obfuscation. One can have verbose code that is obfuscated. MemAlloc for strings, switch/case/break, and the for loop syntax are verbose.. not terse. Ruby is terse. Obfuscation, as we say once more, is not directly related to terseness. C is verbose, not terse. The very word TYPEDEF is even more verbose than just TYPE.
The inside out nature of declarations is unnecessarily inelegant. Declaring static in front of each private function is also inelegant since the function name is not visible until one first reads the function result type, and the word static, and any calling conventions, and finally the function name. The most important word for programmers who scan source code is the function name, and C treats it as unimportant, displaying somewhere else in the center or at the end of the line hidden away inside other less important words. The eyes should not be used for scanning for needles in hay stacks in source files or prose. This inside out declaration nature of procedures causes eye strain and incomprehensible inelegant code. It is all unnecessary, which is even sadder.
The lack of types such as boolean in standards before C99 also cause confusion and non elegant solutions. This is also completely unnecessary, and can only be explained by brain damage or incorrect medical prescriptions.
The Final Word
The C language is in a similar state as Standard Pascal, in that the language must be extended. The C standards are not followed by the most popular compilers in the world, and therefore pure C is no longer used, just as pure Pascal is virtually no longer used.
The C language is closed. In its pure and even modern form, C is a toy language, designed for teaching how to build sloppy, error prone, unsafe, and buggy programs.
The C language wasn't designed for the future. Advocates claim that it was designed in the 1970's and that is the reason it lacks ABC or XYZ since ABC or XYZ wouldn't have ran on the hardware back then. This is exactly the point. "C" is a language that is for "back then", and it was not designed for the future. Standards committees keep C as C.
Clap hands for them adding boolean in C99. Wow, one step for man.
Note this article was written long ago back in early 2000's, C is still a fairly simple language compared to a lot of other complex crap nightmares out there. The title is partially tongue in cheek. [update 2016: thank goodness for Go Lang, it's pretty cool. Hats off to Rob Pike and others, as Go Lang addresses many of the concerns in this article (Hi Go Lang Developers, are you reading my mind?)]
See also: What The Fuck Is This C Shit
|