>> | No.95233 The C language is particularly rich with ways of writing a program that totally hides the original design intent and makes it easy to shoot yourself in the foot: - C has too much undefined behavior: while it is understandable, that some CPU don't have 32-bit integers or signed shifts, and compiler optimization may favor one argument evaluation order over the other, these features should be explicit and while 32-bit integers are inefficient on 16-bit platform, changing int to 16 bit would break portability, especially when 32-bit integers could be emulated with little over head argument order could be declared explicitly, still leaving room for all the optimizations.
- Weak-typing: C implicitly converts between floats and integers, despite they being completely different and incompatible objects, while conversion between them incurs high runtime cost. This leads to inefficient code, confusion and complicates semantics. Moreover, C allows passing integer, where pointer is expected, leading to segmentation faults.
- C programmers must write the details of buffer overflow protection into their usage of buffers EVERY TIME they write input buffer code. This means that programmers simply write buffer code with out limit protections, which is exceptionally flawed in terms of software design and quality standards. We don't have to imagine how many times this has caused bugs, just look to the number of security breaches and patches with "buffer overflows" as the access point. Null-terminated strings (c-strings) are inefficient and insecure, due to requirement of calculating length every time and inability to correctly handle byte of value 0, so malicious or incorrect user-input could lead to buffer overflow with segfault or unexpected string termination. While malloc always stores the size of allocated array, it isn't available to the user, resulting in a 4 to 8 bytes overhead and a few CPU cycles per allocation, because user would have to use his own size variable.
- Despite being called "portable assembler", C doesn't expose some of the common assembler's features, with major deficiency being the absence of non-local gotos and generated gotos. Implicit function call stack constantly getting in the way, coupled with no well defined calling convention and function/stack ABI, impedes write exception handlers, garbage collector or doing stack traces using plain C; broken stack ABI is the single reason why languages like Scheme or C++ cannot be compiled down to plain C, without assembly hacks; this C's deficiency also gives birth to slow, memory leaking setjmp kludge. Moreover, macro-assemblers implement more complex structures, like functions and do-while-loops using macros, while C requires them to be a part of core language. There is also no way to get the size of compiled function or force it to reside at certain memory address, making it impossible to write certain boot-loader and OS code in C. Data alignment and calling-convetion features are very implicit and confusing, while in real assembler they are explicit. Array aliasing causes major performance hit, because compiler allows overlapping arrays, meaning CPU cannot keep array elements in registers - that is compared to Fortran or Common Lisp, where arrays are solid objects, which never have pointers pointing inside of them. The general theme of C-vs-assembly argument is C being both low level and not being low-level enough, while lacking capability for abstraction, macro assemblers have, making C a badly designed half low-level language with no potential for growth - a castrated assembly with infix operators, implicit coercion and stack bolted on top, constantly conflicting with it's own principle of "not paying for what you don't use, and not using what you don't pay for". C compares badly even to earliest language - Fortran, with Fortran beating C in every clause, including performance and hardware features exposure (http://www.ibiblio.org/pub/languages/fortran/ch1-2.html)
- Syntax of C, although mimicked by other mainstream languages, has often been criticized. For example, Kernighan and Ritchie themselves say, "C, like any other language, has its blemishes. Some of the operators have the wrong precedence; some parts of the syntax could be better." Some questionable choices of operator precedence, as mentioned by Kernighan and Ritchie, such as == binding more tightly than & and | in expressions like x & 1 == 0, which would need to be written (x & 1) == 0 to be properly evaluated. Moreover, it is easy to mistype == as =, leading to common bugs like "if (Byte = 123)" permeating C code. Ritchie's "declaration reflects use" idea was proven to be extremely unintuitive and confusing, particularly for function pointers. C inherits dangling else problem from Algol, because C's syntax allows ifs without matching else clause; a good design would be to solve the else problem by forcing every if to have matching else and introducing `when X then Y` construct for "no-else" conditionals, but C doesn't have good design. Finally, a large number of cryptic compound operators, such as +=, -=, *=, ++, ?:, don't make C easier to read or learn.
- Deployment of C software requires especially complicated system of Make, ./configure, Autoconf, Automake and M4 macros (in addition to C preprocessor), with numerous other external tools, like perl, flex and yacc, generating "probes" against compilation environment. Linking process isn't straightforward either, as there is no standard ABI to shared code and even static libraries, especially when C++ kicks in, leading to DLL hell and the like conditions. C code and tools produce programs that are inaccessible to outside world and cannot be inspected during runtime. Once compiled C programs are static, difficult access and upgrade and have horrible ABI, through #include files, instead of robust module system, like the symbol package system of Common Lisp. C preprocessor #include system relies on a myriad of unobvious hacks, like "#ifndef COMMON_H", "#pragma once" and "#line number filename" - combined they wont make language simple or user-friendly.
- In addition, People are taught to program in C, instead of Lisp, because "industry demands it", so our education is based on the whims of short-sighted and blinkered industry bosses. More fundamentally, the majority have difficulty dealing with ideas of reflection, meta-programming, homoiconicity, and the like, and it's hard to see the point if you don't understand.
|