C Programming/Weaknesses of C
From Wikibooks, the open-content textbooks collection
C is an extremely efficient minimalist language. Unfortunately it suffers from weaknesses that occasionally are offset by the use of another language in parallel for added flexibility and power, like the combination of Emacs-LISP and C used for Emacs.
Below are several weaknesses of ANSI C - (that sometimes also form the foundations of its strengths), some minor and some major:
- Lack of differentiation between arrays and pointers
- The very first C (around 1973) did not have arrays at all; modern implementations are contiguous areas in memory accessed with pointer arithmetic(note: a declared array cannot be assigned to like a pointer), which served as an escape from the previous need to declare arrays with a constant size. This ability, however, can cause problems with careless use.
- Arrays do not track their length
- A consequence of the above. This means that the length must be tracked manually. There is no way for a function to discover the length of an array it was given: So the function must be given the length, perhaps in a separate variable or struct, or else hope that whoever allocated the array made it plenty long enough. Because of this, most implementations do not provide automatic bounds checking, and manual bounds checking is error-prone.
- A VLA -- variable length array -- can only be used for function parameters and auto variables. VLAs cannot be allocated on the heap[citation needed] or used inside a structure (except as the last item in the structure). It's not possible to define a structure that corresponds to the standard Forth dictionary definition (which has 2 variable-length parts), except as an undifferentiated array of
char. - arbitrary size built-in 2D or 3D arrays not widely supported
- This has been solved with C99 variable-length arrays, although many C compilers still do not support VLAs. Without VLAs, there is no way for a function to accept the built-in 2D or 3D arrays of arbitrary size. In particular, it's impossible to define a function that accepts
int a[5][4][3];on one call, and later acceptsint b[10][10][10];in a later call. Instead of using the built-in 2D or 3D array data type, C programmers use some other data type to hold (mathematical) 2D or 3D arrays of arbitrary size (multi-dimensional arrays) -- see C Programming/Common practices#Dynamic multidimensional arrays for details. - No formal String data type
- Strings are character arrays(lacking any abstraction) and inherit all their weaknesses(structs can provide an abstraction, to an extent).
- Weak typedef
- typedef does not create a new type but only an alias, thus it serves solely for code legibility. However, it's possible to use single-member structs to enforce type safety.
- No garbage collection
- As a low-level language designed for minimum overhead, C features only manual memory management, which can allow simple memory leaks to go on unchecked.
- Local variables are uninitialized upon declaration
- Local(but not global) variables must be initialized manually; before this, they contain whatever was already in memory at the time. This is not terribly unusual, but the C standard does not forbid access to uninitialized variables(which is).
- Unwieldy function pointer syntax
- Function pointers take the form of
[return type] [name]([arg1 type])([arg2 type]), making them somewhat difficult to use. Typedefs can alleviate this burdensome syntax. For example,typedef int fn(int i);. - no reflection
- It is not possible for a C program -- at runtime -- to evaluate a string as if it were a source C code statement.
- nested functions not standard
- although many C compilers do support nested functions.
- no anonymous function definitions
- no formal exception handling
- Some standard functions return special values that must be manually handled. For example,
malloc()returns null upon failure. For example, one must store the return value ofgetchar()in anint(not, as one might expect, in achar) in order to reliably detect the end-of-file. Too many programs don't include error handling. Such programs seem to work fine most of the time, but and crash or otherwise malfunction when exceptional cases occur. POSIX systems often usesignal()to handle some kinds of exceptions. (See {where can I read more about signal()?} for details). Some programs usesetjmp(),longjmp()orgototo manually handle some kinds of exceptions. (See C Programming/Control#One last thing: goto and C Programming/Coroutines for details).