Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
IDENTIFIERS
Identifiers are names of variables, functions, defined types, structures and unions, enumeration
constants, statement labels and preprocessor macros
Identifiers are composed of alphanumeric characters, and _. Must start with a letter, and are case
sensitive. ANSI C requires at least 31 characters to be significant. By convention, constants are
in CAPITALS. External identifiers may be restricted to 6 significant characters only, and may be
case insensitive
DECLARATIONS
All identifiers must be declared before they are used.
Scope: region of a program over which that declaration is active
functions have scope of the entire (multi-file) program. i.e. they are extern by default.
identifiers declared within a function or block have block scope, or local scope
statement labels have function scope
all other identifiers have file scope
Visibility: the declaration of an identifier is visible at some point in a program if the use of that
identifier at that point causes it to be associated with that declaration. An identifier is usually
visible throughout its scope, however it may be hidden if a second identifier of the same name
but a more restricted scope is declared.
Extent (or Lifetime): variables and functions have existence at run-time: they have storage
allocated to them. The extent, or lifetime, of an object is the is the period of time for which
storage is allocated. It has:
static extent if storage is allocated when the program begins execution, and storage
remains allocated until program terminates. All functions have static extent, as do
variables declared outside any function. Variables declared inside blocks (functions)
may have static extent (if they are declared static ).
local (or automatic) extent if storage is allocated on entry to a block or function, and
destroyed on exit from that block or function. If variable has an initialiser, it is reinitialised each time the block or function is entered. Formal parameters have local
extent, and variables declared at the beginning of blocks may have local extent. auto
variables have local extent.
dynamic extent if storage is explicitly allocated and de-allocated by programmer. Library
routines (e.g. malloc() and free()) are used to do this dynamic memory management.
Initial Value: variable declaration allocates storage for the variable, but does not initialise it
good practice to initialise the variable when it is declared, so it is always initialised to
something definite.
also good practice to re-assign of change a value just before the variable is used!
a static variable is initialised only once (when the program is loaded), and retains its
value even if the program is executing outside the scope of the static variable.
CONSTANTS
Integer
12 (decimal) 014 (octal - starts with zero) 0xC0 (hex) are the same integer.
no binary integer type - easiest to use hex numbers instead.
Character
char const e.g.: 'R' has the value of the ASCII code for R (52 0x34 064).
char consts can be represented as octal or hex escape sequences:
\0x34 or \064 are both equivalent to 'R'.
some non-printing ASCII constants are pre-defined.
e.g.: \n is the ASCII new line character.
String
Floating point must contain decimal point and/or E. e.g.: 1.23 or 123E-4
Suffix of u or U denotes unsigned, l or L denotes long. e.g. 12L or
12.345L
NAMED CONSTANTS
It is extremely poor practice to use "magic numbers" in code. Use an editor to search for, and
then destroy, any "magic numbers" that appear in your code. Replace them with named constants
that are defined once, and collect all of the #defines in the same place (usually a header file).
#define
#define
#define
#define
ARRAY_LENGTH
BLOCK_SIZE
TRACK_SIZE
ERROR_MESSAGE
2500
0x100
(16*BLOCK_SIZE)
"** Error %d: %s. \n"
VARIABLE DECLARATIONS
All variables must be declared before they are used. Format of declaration is:
<type qualifier> <storage class> <type> <name1>, ..., <namen>;
Default is signed auto int if variable declared in a function. A variable declared outside a
function, including the main() function defaults to signed extern int
VARIABLE TYPES
char
(-27 to + 27 - 1)
(0 to 28 - 1)
unsigned short
also called unsigned short int
unsigned integer, size is architecture dependent.
16
on 16-bit machine is 0 to 65,535 (0 to 2 - 1)
int
unsigned int
also called unsigned
unsigned integer, size is architecture dependent.
16
on 16-bit machine is 0 to 65,535 (0 to 2 - 1)
long
unsigned long
also called unsigned long int
signed integer, size is architecture dependent.
on 16-bit machine is 32-bit 0 to 4,294,967,295
(0 to 232 - 1)
ANSI C specification requires only that a char is at least 8 bits, a short or an int are at
least 16 bits, and a long is at least 32 bits.
The ranges of permissible values of the various integer types in a given implementation are
defined in limits.h.
float
double
floating point number, often 8-byte: 1.7 E 308 (15 decimal digits)
( 7 decimal digits)
long double
The form of floating point types is not specified, nor are they even required to be different!
The ranges of permissible values of floating point types in a given implementation are
defined in float.h .
void
POINTER TYPES
For any type T, a type pointer to T may be formed. A variable of type pointer to T holds the
ptr;
ptr is a pointer to an object of type char
(* fp)(char); fp is pointer to function with char argument that returns int
(* ap)[];
ap is a pointer to an array of double
Note that some function declarations look a bit like pointer declarations...
int *
int *
tool(char);
sally[];
A generic pointer type is defined. This generic pointer can be cast to a pointer to an object of
any type. In ANSI C, the generic pointer type is void * (read this as pointer to void).
A null pointer explicitly points to no object or function. The standard header files stddef.h
contains the definition of the null pointer, NULL. Since the value of NULL is implementation
dependent, one should test for equality to NULL, rather than to 0 or 0L or (void *) 0.
It is good practice to ensure that all pointers have the value NULL when they are not pointing to
static
extern
variables are local to the block (includes function) in which they are declared.
memory is allocated when variable is used, released when function terminates
i.e. passed through the stack. Efficient use of memory, but function "forgets"
between invocations. i.e. variable has local (automatic) extent.
variable retains its value between invocations, as it is allocated a fixed address in
RAM. The variable has static extent.
if declared within a function, scope is that function. i.e. "private" to the function.
if static variable declared outside all functions, scope is file in which it is
declared. i.e. global inside file, but not visible outside file - a module variable.
scope of static function is file in which declared: the name of the function is not
exported to the linker. i.e. a static function is "private" to the file.
extern functions and variables have external linkage: their names are
exported to the linker, so that they are visible outside the present file.
extern objects have static extent
variables declared outside all functions are extern by default.
local variables with the same name take precedence within their scope.
extern function is defined outside of the present file. Scope is global.
register use a CPU register to store the value, if possible, to give fast access to a variable.
TYPE QUALIFIERS
const
volatile object can be modified by something outside the scope of the program, such as
an external hardware event. Instructs the compiler not to optimise a volatile
used to define new data types in terms of those already defined. Does not
allocate storage.
extremely useful for building portability into code!! For example,
typedef
unsigned char *
prompt_t;
struct
a structure is a derived data type consisting of one or more named members of the
same or different data types. For example,
struct material_s
{
float density;
float modulus;
float yield_strength;
};
/* definition
*/
/* declaration
*/
/* use
*/
union
a union is a derived data type capable of containing, at different times, any one of
several different data types. It is like a structure big enough to contain any of the
members - only one member can be stored at any time. For example,
union mixed_u
{
char
c;
int
i;
long
l;
};
union mixed_u
x;
x.c = 27;
x.l = 934273421;
/* definition
*/
/* declaration
*/
/* use this
/* OR this !!
*/
*/
enum
enumerated types have values that can range only over a set of named (int)
constants called enumerators. For example.
enum state_e {false, true};
enum state_e sleeping;
/* definition
/* declaration
*/
*/
sleeping = false;
/* use
*/
Enumerated types take sequential integer values starting from zero. In the
example above, false==0 and true==1. Alternatively, can initialise values:
enum colour_e {red = 2, green = 4, blue = 8};
BITFIELDS
Structures used in machine-dependent programs that must force a data structure that
corresponds exactly to fixed hardware features.
Bit fields may be of type int, signed int or unsigned int.
"Padding" may be required to create holes.
Ordering of bit packing will depend on whether the target computer is a "big endian" or a
"little endian". Bitfields are therefore likely to be non-portable.
#define SET
#define CLEAR
typedef struct
{
unsigned int
unsigned int
unsigned int
unsigned int
unsigned int
unsigned int
unsigned int
unsigned int
unsigned int
} port_even_b;
1
0
bit0:
bit1:
bit2:
bit3:
bit4:
bit5:
bit6:
bit7:
odd_byte:
struct port_even_b *
ad_command->bit4 = SET;
1;
1;
1;
1;
1;
1;
1;
1;
8;
/* low byte
ad_command
*/
*/
OPERATORS
arithmetic + - * /
Integer division truncates.
e.g.: 99/100 == 0;
assignment =
Can use multiple assignment.
Short form of assignment:
is the same as
increment ++ increment
-- decrement
Pre-increment is ++b;
Post-increment is b++;
bitwise
5/3 == 1;
e.g.: a = b = c = 0;
e.g.: a += b;
a = a + b;
~ NOT
& AND
| OR
^ XOR
Bitwise operators are useful for setting and testing bits. Some examples:
#define EMPTY
#define CTS
0x04
0x20
shift
i << j
i >> j
/*
/*
/*
/*
*/
*/
*/
*/
If i is unsigned, or signed and positive, zeros are shifted in to replace bits shifted out
(logical shift). If i is signed and negative, some compilers do arithmetic shift
useful to do
#define
#define
sizeof
FALSE
TRUE
0
1
sizeof(object) returns the size of the argument measured in memory storage units
(usually bytes). The type returned by sizeof() is size_t, defined in stddef.h.
MIXED-TYPE EXPRESSIONS
legal but potentially dangerous: compiler automatically performs type conversion so all
variables in expression are of same type before expression is evaluated. Can cause unexpected
results if you don't think like a compiler!!
preferred types are starred, below. In a mixed-type expression, non-preferred types are first
promoted to a preferred type, then all are promoted to the highest type.
double
float
long
unsigned int
int
char
automatic conversion across assignment: value on RS converted to type on LS. For example:
an_int = a_char;
a_char = an_int;
an_int = a_float;
is OK
will (probably) truncate high byte of an_int
is suspect, if a_float > 32,767
a "cast" is used to explicitly change a variable's type. The classical example is casting a
generic pointer during dynamic memory allocation. The library function malloc() returns
void * .
material_s *
This statement only allocates memory and initialises the pointer: the memory is not
initialised.
Another example is the use of a cast in defining the type stored at an absolute address:
#define SBUF
if you have to use lots of casts, there may be something wrong with choice of variable types.
STATEMENTS
simple statement e.g.:
x = a + b;
/* Note semicolon!
*/
/* This is a comment.
/* Comments can NOT be nested
*/
*/
}
the statements within the braces { ... } constitute a block.
if else construct
if (<expression>)
<statement1>;
else
<statement2>;
the else is optional
Conditional Expression
r = <expression> ? <statement1> : <statement2>;
is equivalent to
if (<expression>)
r = <statement1>;
else
r = <statement2>;
/* if TRUE
or
!= 0
*/
switch construct
switch (<integer expression>)
{
case <integer constant1>:
<statements>;
case <integer constant2>:
<statements>;
case <integer constant3>:
<statements>;
default:
<statements>;
}
<integer constant> should be set up in a #define preprocessor directive, or as an
/* or ...
*/
each group of <statements> will usually have to end with break; The break statement
causes execution of the smallest enclosing while, do, for or switch to terminate.
the default: block is optional, but is good practice.
while construct
while(<expression>)
{
<statements>;
}
*/
do while construct
do
{
<statements>;
} while (<expression>);
*/
for construct
for (<loop initiation>; <loop test>; <loop action>)
{
<statements>;
}
execution of a break statement causes the transfer of control to the first statement
following the innermost enclosing while, do or for loop, or switch statement.
execution of a continue statement causes the transfer of control to the beginning of the
innermost enclosing while, do, or for loop statement. Execution of the affected loop
statement may continue following re-evaluation of the loop continuation condition test.
a continue statement has no interaction with an enclosing switch statement.
a goto statement may be used to transfer execution to any statement within a function:
/* some statements */
goto find_the_label;
/* lots of statements ... */
find_the_label:
/* more statements ... /*
Use goto with extreme care, and only when really necessary. It can lead to unreadable
"spaghetti" code. In particular, never use a goto to branch into the body of an if , a
switch, a for or a block from outside the block.
it is far better style to use break, continue and return statements in place of goto.
FUNCTIONS
Function is block of statements invoked by a single call.
Each function should be declared before it is used. This is nor required by ANSI C, but is
strongly recommended, as it allows the compiler to perform type checking. The "prototype"
form of a declaration is
<type qualifier> <storage class> <type> <name> (<argument list>);
func(unsigned);
long *
junk(double, float);
void
monk(float *, long);
double *
punk(void)
Note that some pointer declarations look a bit like function declarations ...
int
void
(* fp)(void);
(* fp)(void);
that multiple calls of the same function within an expression are not guaranteed!
Function definition
Consists of declarations and statements that make up the function.
Format of a function definition is:
<type qualifier> <storage class> <type> <name> (<argument list>)
{
<local variable declarations>;
<statements>;
return (<expression>);
/* omit if function returns void
}
*/
where, for the prototype form of a definition, <argument list> is of the form
<<type qualifier> <storage class> <type> <name1>,
<type qualifier> <storage class> <type> <name2>, ...,
<type qualifier> <storage class> <type> <nameN>>
Function definitions may not be nested.
A function that can have a second copy of itself invoked before the first copy has terminated is
called reentrant. A recursive function must also be reentrant. A function that uses only
auto variables will be reentrant. Any functions invoked by an interrupt must be reentrant.
Note that not all compilers produce reentrant code!
Pointer declaration
Pointers are declared as
<type> *
<name>;
<type> *
*/
for example:
unsigned char *
ptr;
*/
*/
*/
Pointer initialisation
ptr = &var;
*/
*/
*/
*/
*/
*/
Pointer indirection
unsigned char *
unsigned char
unsigned char *
c = 55;
ptr = &c;
d = *ptr;
++*ptr;
ptr;
c, d;
ptr;
/* ptr is the address of c
/* d = value of variable pointed to by ptr
/* Same as ++c
*/
*/
*/
Common usage of pointers is when a function must change values of arguments ("call by
int
first
*);
= 23,
*/
*/
*/
*/
second = 44;
swap (&first, &second);
void swap(int*
{
int temp;
temp = *a;
*a = *b;
*b = temp;
}
a, int*
b)
different type.
The type of the result of subtracting one pointer from another is architecture dependent. It is
of type ptrdiff_t, defined in stddef.h.
/* Array of 10 ints;
a is a pointer to a[0]
*/
arrays are always passed by address, and the function can alter the values of array elements.
program to allocate a region of memory from the "heap" (unallocated RAM), and deallocate it
when no longer required.
memory allocation functions return a generic pointer (void *). The user may then use a cast
to convert the pointer to another pointer type.
malloc() alocates storage for one object of size size, as determined by sizeof(size). The
memory is not initialised. If the memory cannot be allocated, a NULL pointer is returned. For
example, a function to allocate (memory for) a new object of type object_t :
object_t *
{
object_t *
new_object_t(void)
objectptr;
once memory has been deallocated, it is an error to use this deallocated memory.
realloc() can be used to increase or decrease the size of a region of previously allocated
memory. If the size is increased, any information previously stored may be retained (if
realloc() returns the same pointer). If realloc() is called to reallocate a region to size
zero, the action is the same as free().
PREPROCESSOR DIRECTIVES
The preprocessor operates on the source file before it is parsed by the compiler. Preprocessor
directives tell the preprocessor what to do. Common directives include:
#define
defines an identifier to be equal to a token sequence. For example:
#define PROMPT
#define PIO_C_A
'>'
0x0021
/* defined constants */
#define IO_PORT3
#define stop_m()
#define square(x)
(outp(PIO_C_A, HALT);)
((x)*(x))
/* contents of
absolute address */
/* macro definition */
/* brackets for correct
macro expansion! */
you should have NO numerals ("magic numbers") in source code use a text editor to search
for them. Use #defined constants and collect them together in a header file.
#ifdef
can test to see if a macro is defined (i.e. non-zero). Useful for debugging instrumentation:
#ifdef DEBUG
/* debugging statements ... */
#endif
#include
causes the entire contents of a file to be included in place of the include statement. For
example:
# include <filename>
# include "filename"
/* system file
/* user's file
*/
*/
That's all for a shortish introduction. Two excellent general references on the C language are
Kernighan, B.W. and Ritchie, D.M. The C Programming Language. 2ed, Prentice Hall,
Englewood Cliffs NJ, c. 1990. Make sure to get the 2nd (ANSI) edition! Dennis Ritchie
wrote the C language.
Harbison, S.P. and Steele, G.L. C: A Reference Manual. Prentice Hall, Englewood Cliffs NJ,
1991.
A nice book on program construction is
McConnell, S.J. Code Complete. Microsoft Press, 1993.
Thanks to Chris Bujor for reviewing this document, and for his many helpful suggestions. Any
errors or omissions remaining are, of course, my responsibility.
DCR
15 July, 1996.
File: C.DOC