The recommended C book for this unit is the wikibooks one at wikibooks.org/wiki/C_Programming/
These slides provide a few general notes, then notes on some of the sections of that book - if these notes don't answer your questions, ask Ian
Does this unit cover all of the C language?
Yes, except for some of the more obscure features (union, register, restrict, volatile) and its use in device drivers or embedded systems
Pro C: total control over efficiency, processor, devices at the lowest level, base language on all computers
Con C: undefined, unspecified, platform dependent and locale dependent behaviour
The unit doesn't cover C++ or C# etc. from the C family
C++ is C with no features removed, and many more features added to support object oriented programming, so it is too complex (and some things are harder than C!)
But it has some niche uses (gaming, crypto, ...) and if you stick to a well-chosen subset, C++ can be fine, but maybe it is better to jump straight to Java
C# is technically an open language but, in practice, has far too much of a Windows bias to be thought of as platform independent (except perhaps for Unity)
According to computer science theory, C is universal, i.e. capable of carrying out any computation
So there is nothing in principle you can do in C that you can't do in other languages, and vice versa
In practice, some things seem more convenient in one language than the other
You will meet other languages in the future, but C provides a universal jargon which you will be expected to understand
This section of the book isn't very good: our reasons for teaching C (which is old) are:
You may well find C useful for:
It is important to realise that every historical summary is inaccurate, misleading and biased
This section of the wiki book is no exception, and it particularly misses out what has happened to C since the earliest days, but it is a reasonable starting point
The only compilers you should consider are clang and gcc, not Tiny C or Visual Studio which are far too non-standard for cross-platform work
Our recommendation for a C programming environment for Windows is Cygwin or Bash (see aside: computers)
The section mentions IDEs, but says not to use them unless confident - so don't use them (unless confident)
The section recommends Dev-C++ for Windows
But that is an IDE, and IDEs should be avoided (unless you are confident)
And it suggests MinGW for Windows, but that is old and causes lots of problems; Cygwin or Bash is recommended
Many examples in the book have no program comment, which makes them rubbish programs
The compiler commands in the book do not include the option
-std=c11
, which you must use in this unit
In fact they don't specify a standard at all, which is a disaster for cross-platform programming
The essential option -Wall
is barely mentioned, and other
recommended options such as -pedantic
or -g
are not mentioned
The book uses the Allman indent style rather than the K&R indent style (see Wikipedia)
Either style, or any other, is OK, but set up your editor to make sure your indenting is consistent
The K&R style is used in this unit, partly because programs fit better on slides
In team programming, it is important for all members of a team to (a) follow company policy if any or (b) agree on the same style and (c) not argue about it
The book uses int main(void)
- the void
does no
harm, but is arguably technically incorrect
It means that no calls to main
should have any arguments, whereas
main()
means any number of arguments
But (a) main
is called by the system with two arguments (b) there
aren't going to be any calls to main
from your own code (c) in general, calling
functions with too many arguments isn't a common source of bugs (d) across modules,
it is the declaration (i.e. signature, or prototype) where void
matters, not the definition
The book uses end-of-line comments, which sets a bad example for real programs - never use them (they are usually a sign that your code isn't readable enough, and keeping them lined up and consistent with the code, is unnecessary maintenance effort)
There are also comments inside function definitions - you should hardly ever use them - comments should come before functions
A global variable is used (to illustrate scope) - global variables are banned in this unit
These days, you hardly ever need to compile modules separately, so the details of how to do it can be ignored
The book shows some multi-line comments with decorative asterisks - this isn't recommended unless your editor maintains them neatly with no effort
For this unit, we recommend using the multi-line style for a program comment at the top of a program or module, and the single-line style (even if you need two or three lines of comment) in front of a function - that allows you to use a multi-line comment to comment out large sections of a program during debugging
This section seems to recommend against newer standards because "some compilers still don't support them", and "it is uncommon so may irritate other programmers"
But using no standard is stupid for cross-platform programs, the newer standards have been around a long time now, C11 is technically the only C standard, and C11 is supported by decent compilers. It is time for all C programmers to use C11 (otherwise you will irritate forward-looking programmers).
Variable names should be as readable as possible, and also as compact as possible as long as that doesn't make them less readable - many bugs are caused by poor or confusing choices of variable name
Short, unabbreviated, single words are often best - one-letter names can be OK for short-lived generic variables
For multi-word variable names, there is the underscore style (some_number
) and the
camel case style (someNumber
) - use whichever you like
The const
keyword is mentioned - use it for constants
The #define
mechanism is mentioned - don't use it, because it is a death trap
Don't use auto
- it is never necessary
Don't use register
- let the compiler optimise
Don't use volatile
- except in a device driver
The static
and extern
keywords are not needed until we talk about programs
with separate modules
Why is #define
dangerous?
#define plus1(n) n+1
With this definition, 2*plus1(3)
becomes 2*3+1
which
is wrong
So, put brackets round the entire replacement text:
#define plus1(n) (n+1)
Why is #define
still dangerous?
#define twice(n) (2*n)
With this definition, twice(3+4)
becomes (2*3+4)
which
is wrong
So, put brackets round each argument appearance:
#define twice(n) (2*(n))
Why is #define
still dangerous?
#define max(m,n) ((m)>(n)?(m):(n))
With this definition, max(2,in())
becomes (2>in() ? 2 : in())
which
is wrong if in
is some function which reads input
So, avoid arguments with side effects or, better, just don't use
#define
at all (use the inline
keyword)
Is there any downside to using const
for constants?
Technically, const
means "read-only" rather than
"constant", so values could be read in, or changed indirectly via
pointers
So you can't use const variables in switch statements (and the compiler isn't able to optimize code by knowing the values at compile time)
Is there a 'nice' way of defining constants which allows their use in switch statements?
Yes, but only for integers and integer-compatible values
enum day { Mon, Tue, Wed, ... }; enum { width=100, newline='\n' };
When you define an enumerated type, it usually defines constants 0, 1, ... but you can give values to the constants, and the enumeration can be anonymous
Is there a 'nice' way of defining functions with full compiler optimisation?
Yes, use the inline
keyword
inline int max(int m, int n) { return m>n ? m : n; }
Across modules, add static
and put the whole definition in a
header file or, better, use the -flto
compiler option for
cross-module optimization
The book has examples of printf
without the final \n
in the string
On some systems, that causes printing to be delayed until a \n
is used in a later call, which means that
the output may not appear when you expect it, which can make debugging
exceptionally difficult
Always use a final \n
except when you are
deliberately building up a line of output in pieces
The section mentions printing out tabs using \b
This is extremely unreliable, being heavily dependent on the operating system and other environment issues
Any use of the actual tab character ('hard tab') is ambiguous and unreliable, so it is best to avoid them altogether, and use spaces instead
For example, you should set your editor to 'soft tabs'
You have to be very careful when using the scanf
function, in
fact it is best to avoid it
It was designed (a) to treat spaces and newlines the same (b) to be used non-interactively (c) without regard for security loopholes
If you want to play safe, or you care about the line structure of the input,
or if you want your program to respond to each line typed in,
use fgets
to read a line in and then sscanf
to extract
the items from it
The section has an example of using scanf("%s")
There are at least three problems with this:
%s
reads in a word, not a line, stopping at a spacescanf
call doesn't
read in the newline, which mucks up the next callSo use fgets/sscanf
and avoid scanf
One example shown is:
printf("Please input an integer value: ");
The idea is to print out a prompt without a newline, and for the user to type their response on the same line
But on some systems, the text won't appear until the next
printf
, so the user won't see it in time
For safety, add fflush(stdout);
after the printf
This section contains a lot of grammar jargon
Also, it is like a reference manual, listing all the operators, regardless of whether you need them yet
Precedence refers to the rules which the compiler uses to decide what order to do things in
For example, in 2+4*10
the multiplication is done first giving
42
, not the addition which would give 80
, because *
has a higher precedence
If in any doubt, add brackets, e.g. write 2+(4*10)
or
(2+4)*10
to make it clear which you mean
A cast in C is something like (int) pi
which changes a value to a given type
Casts are very unsafe, because any basic type can be cast to any other, whether it makes sense or not, and information can easily be lost or mangled
It is usually best to regard using a cast as a failure to find a better and safer way of expressing what you want, and you should add a comment to explain
The book suggests that the shifting operations can be very fast, e.g.
n<<1
might be faster than n*2
But you shouldn't write n<<1
when you mean
n*2
That's because (a) it is less readable and (b) the compiler
can optimize better than you can and will automatically convert your n*2
into n<<1
only if it is genuinely better (just use the -O2
compiler option)
The book says that C doesn't have true and false, and uses 1
and 0
instead
But it doesn't say that you can use #include <stdbool.h>
which provides the type bool
as a synonym for a suitable integer
type, and true
and false
as synonyms for 1
and 0
Use these to make your programs more readable
This section seems reasonably accurate
This section fails to mention the stdbool.h
header
It mentions goto
- never use it
Otherwise, it is reasonably good
See the end of the statements chapter of the lecture notes on how to avoid uncontrolled jumps
The early examples in this section have printf
s without
newlines again - don't do it
There are functions with no arguments defined with argument list
(void)
- as before, that's only necessary in declarations
not definitions
The book has some brief information about recursion
For most problems in the early parts of the unit, you can use either recursion or loops - feel free to use either
In branching problems, e.g. divide-and-conquer algorithms like quicksort or mergesort, or traversal of tree data structures, recursion comes into its own as the most readable, convenient and efficient approach
The static
keyword is mentioned again
It is not needed until we start on multiple modules
Just for the record, it makes sure that a function is local to the module it appears in, and isn't visible to the rest of the program
C's standard library consists of a collection of functions, summarised in the book
To check the official full set of library headers and functions, see the C11 standard (chapter 7)
To find other documentation for a function, type C and its name into Google (a C++
version is often OK) or use the man
command
Variable length argument lists (varargs) are an advanced topic
printf
is the main common example
You hardly ever need to define a varargs function for yourself
The standard library of support modules has already been covered, so this section just gives extra advanced or background information
It mentions the POSIX library, which you can use with care, but it is very badly described
The section is reasonably accurate, but not very clearly explained
The section is reasonably accurate, but not very clearly explained
Error handling needs to be taken seriously if you want to produce robust production programs
The main thing is to read the conventions for each standard function you
use, test its return value or whatever to see if there was an error, and
do something sensible (e.g. call perror
and exit
)
Other issues in this section are advanced
A useful technique that the book doesn't mention is wrapper functions:
FILE *fp = notNull(fopen(...));
The notNull
function checks that the call returns a non-null
result:
void *notNull(void *p) { if (p != NULL) return p; ... report error and exit ... }
Newlines are mentioned: see aside: characters
Redirection is mentioned: see aside: unix commands
The section is written like a reference manual - it doesn't highlight the simple things first
You will get a better impression of simple approaches from the lecture notes: chapter: input & output
The section is reasonably accurate, but not very clearly explained
Don't use "wide character" strings, use ordinary strings containing UTF-8
This section doesn't make it very clear exactly what to do to use the maths library
Suppose you want to use the sqrt
function
At the top of the program, put #include <math.h>
On some systems, you must compile with:
clang -std=c11 ... prog.c -lm
The -lm
(include maths library) option must come after prog.c
Most standard library modules are linked with your program automatically
Others may need a -l
option, e.g. -lm
for maths
The compiler deals with each program file or library on the compiling line in left-to-right order
When it reaches a library, it extracts from the library all the functions
which have outstanding calls so far, so if you write clang ... -lm prog.c
nothing will be included from the library
It may be interesting to read this section, but it is advanced, and the modules chapter of the lecture notes will provide a simpler approach to some of the issues
The exercises are worth working through, if you have time
The advanced sections after this are also worth browsing in, if and when you have time