2011 Jan 10
C Tutor | C Coding Standards |
Good and Bad Practices in C Code |
External C Links |
The C programming language is put down by some people because it is called "a high level assembly language". This is not accurate because the C language and C compilers do not use, or directly give access to, every instruction or feature of a computer's architecture (such as I/O, specialty registers: e.g. caches, memory protection, TLBs, and so on).
This is not a real disadvantage, it is an advantage. It would be fatal to incumber the C language (or any language) to be designed to give access to every unique feature of every CPU architecture. This requires real assembly language, and is why the core parts of an operating system are, and must be, written in assembly language. Those modules, which contain CPU specific functions, can be linked and called from ordinary C code. Such assembly language modules require knowledge of such things as stack and function call/return conventions are compiler and CPU architecture specific. These modules are then made into a library whose functions can be called from a function in ordinary standard C modules.
It is considered to be a disadvantage of the C programming language that the compiler will not prevent, or warn, a programmer that they did something wrong (fatal enough for the program to crash). This is because one of the goals of C is to be speed and memory efficient. In this the C language succeeds admirably by not having run time checks.
This means a programmer needs to know what they are doing. Specifically, all data declarations for variables envolving pointers are ambiguous in terms of use and semantics. This situation arises because only the programmer knows the intention that has been designed and implemented. The ambiguity is real and is inherent to computer architecture. Much more is said about this on the C Tutor pages.
In any programming language, with knowledge of function and stack protocols, it is possible to write code in assembly language and link the mdoules compiled (or assembled) by an assembler) to code written in C. Some C compilers have a non standard preprocessor statement or pragmas such as #asm which allow CPU specific assembly language to be written in a C file, which can be compiled and linked as usual. This is of course not portable but will give access to unique architecture features, some of which can include such things as special graphics hardware.
To design a programming language to protect the programmer from themselves is to restict their possibilities. This is not a good thing. Such problems are avoidable by good programming practices (this includes comments and other documentation, as well as some of the concepts in object oriented programming - which good programmers learned to do before the so called OOP revolution). To force object oriented practices where they are not needed can be a burden and can be overdone (as in C++, which is now recognized as a flawed language for more than one reason).
Since programmers are human beings, and not perfect, they will occasionally make mistakes. To help such situations there are free tools such as valgrind to help find such runtime memory usage errors. There are, of course: logical, mathematical and semantical errors which valgrind cannot find. There are source code analyzers which can find many (but not all) such problems.
Finding such errors and fixing them is the debugging problem and requires a software architecture with built in debugging aids to shorten the diagnosis time. Visibility to run time behavior is essential. Once diagnosed, such "bugs", as they are called, can often be fixed rather quickly.
Some problems, however, can take a very long time and require much refactoring because a software architecture change is needed. This is because either the original design is flawed or was not thought out, or because the program has evolved and the original assumptions (not always explcitly know or identified in the beginning) are no longer, or were never, valid.
There are also portability issues such as the size of instances of data types which are CPU specific. The C language gives the sizeof() operator to allow the programmer to write code which is able to compensate for the difference between the size of a variable declared as an int on an 8 bit CPU (e.g. 8051) or a 64 bit supercomputer.
Another portability issue which can arise in certain situations is the "endian" problem. The "Endianness" problem arises in CPU architectures in which memory is addressed at the byte level, but has data objects that occupy multiple bytes. There are two kinds of "Endianness":
Endianness | The address of an object is the address of the |
---|---|
Little Endian | least significant byte |
Big Endian | most significant byte |
2005-2009