http://www.oocities.org/SiliconValley/Peaks/8778/TAU_advprog.html

2 Reminding C

2.1 Basic C Programming

2.1.1 C Program Structure

A C program basically has the following form:
We must have a main() function.
A function has the form:

type function_name( parameters)
{
	local variables declaration
	C statements
}

If the type definition is omitted C assumes that function returns an integer type. NOTE: This can be a source of problems in a program.

So the classic example of the simplest C program:

main()
{
	printf( "Hello world!\n");
	exit( 0);
}

NOTE:

2.1.2 Variables

C has the following simple data types:

C type
Size (bytes) - platform dependent
Lower bound
Upper bound
char
1
-128
127
unsigned char
1
0
255
short int
2
-32768
32767
unsigned short int
2
0
65535
(long) int
4
-231
231 - 1
float 2
-3.2 * 1098
3.2 * 1098
double
4
-1.7 * 10908
1.7 * 10908

On most 32-bits systems all ints are long ints unless specified as short int explicitly.
NOTE: There is NO Boolean type in C - you should use char, int or (better) unsigned char.
Unsigned can be used with all char and int types.
To declare a variable in C, do:

var_type list variables;

e.g.:

int i, j, k;
float x, y, z;

2.1.2.1 Defining Global Variables

Global variables are defined outside of any function in the following way:

short number, sum;
int big_number;
char letter;

main()
{
}

It is also possible to pre-initialise global variables using the = operator for assignment.
For example:

float sum = 0.0;
int big_sum = 0;

C also allows multiple assignment statements using =, for example:

a = b = c = 3;

...which is the same as, but sometimes more efficient than:

a = 3;
b = 3;
c = 3;

This kind of assignment is only possible if all the variable types in the statement are the same.

You can define your own types use typedef. This has greater relevance for creation more complex data structures.

As an example of a simple use let us consider how we may define two new types real and letter. These new types can then be used in the same way as the pre-defined C types:

typedef real float;
typedef letter char;

func()
{
	real a;
	letter c;
}

2.1.2.2 Printing Out and Inputting Variables

C uses formatted output. The printf function has a special formatting character (%) - a character following this defines a certain format for a variable:

%c - character
%d - integer
%f - float

e.g.:
printf( "%c%d%f", ch, i, x);

NOTE: Format statement enclosed in "...", variables follow after. Make sure order of format and variable types match up.

scanf() is the function for inputting values to a data structure: Its format is similar to printf, e.g.

scanf( "%c%d%f", &ch, &i, &x);

NOTE: & before variables. Please remember to include it. It is to do with pointers.

2.1.3 Arithmetic Operations

As well as the standard arithmetic operators (+, -, *, /) found in most languages, C provides some more operators.

Assignment is = e.g.

i = 5;
ch = 'y';

Increment ++, Decrement -- which are usually more efficient than their long hand equivalents, for example:
x ++;
is faster than
x = x + 1

The ++ and -- operators can be either in post-fixed or pre-fixed. With pre-fixed the value is computed before the expression is evaluated whereas with post-fixed the value is computed after the expression is evaluated.

In the example below, ++z is pre-fixed and the w-- is post-fixed:

int x,y,z;
main()
{
	x = (( ++z ) * ( w--)) % 100;
}

This would be equivalent to:
int x,y,z;
main()
{
	z++;
	x = (z * w) % 100;
	w--;
}

The % (modulus) operator only works with integers.

Division / is for both integer and float division. So be careful. The answer to: x = 3/2 is 1 even if x is declared a float!!
RULE: If both arguments of / are integer then do integer division.
So make sure you do this. The correct (for division) answer to the above is x = 3.0/2 or x = 3/2.0 or (better) x = 3.0/2.0.

There is also a convenient shorthand way to express computations in C. It is very common to have expressions like: i = i + 3 or x = x * (y + 2). This can be written in C in a shorthand form like this:
i += 3 and x *= y + 2.
NOTE: that x *= y + 2 means x = x * (y + 2) and NOT x = x * y + 2.

2.1.4 Comparison Operators

To test for equality is: ==
A warning: Beware of using "=" instead of "==", such as writing accidentally.

if ( i = j ) ...

This is a perfectly LEGAL C statement (syntactically speaking) which copies the value in "j" into "i", and delivers this value, which will then be interpreted as TRUE if j is non-zero. This is called assignment by value - a key feature of C.

Not equals is: !=

Other operators < (less than), > (grater than), <= (less than or equals), >= (greater than or equals) are as usual.

2.1.5 Logical Operators

Logical operators are usually used with conditional statements. The two basic logical operators are( there is also unary NOT - ! operator):

&& for logical AND,
|| for logical OR.

Beware & and | have a different meaning for bitwise AND and OR.

2.1.6 Order of Precedence

It is necessary to be careful of the meaning of such expressions as a + b * c. We may want the effect as either

(a + b) * c

or

a + (b * c)

All operators have a priority, and high priority operators are evaluated before lower priority ones.
Operators of the same priority are evaluated from left to right, so that

a - b - c

is evaluated as

(a - b ) - c

as you would expect.
From high priority to low priority the order for all C operators is:

() [] -> .
! ~ -(unary) * & sizeof castings ++ --
* / %
+ -
< <= >= >
== !=
&
^
|
&&
||
?:
= += -= *= /=
,(comma)

Thus

a < 10 && 2 * b < c

is interpreted as

( a < 10 ) && ( ( 2 * b ) < c )

2.2 Conditionals

There are various methods that C can control the flow of logic in a program. Apart from slight syntactic variation they are similar to other languages.

As we have seen following logical operations exist in C:

==, !=, ||, &&

One other operator is the unitary - it takes only one argument - not ! .
These operators are used in conjunction with the following statements:

2.2.1 The if statement

The if statement has the same function as other languages. It has three basic forms:

if( condition)
	statements

or:

if( condition)
	statements1
else
	statements2

or (combination of the previous):

if( condition1)
	statements1
else if( condition2)
	statements2
else
	statements3

For example:

int x, y, z;

main()
{
	x = ...
	if( x > 0)
		y = x;
	else
		z = x;
}


2.2.2 The ?: operator

The ? (ternary condition) operator is a more efficient form for expressing simple if statements. It has the following form:

condition ? expression1 : expression2

It simply states:

if condition then expression1 else expression2

For example to assign the maximum of a and b to z:

z = ( a > b) ? a: b;

which is the same as:

if(  a > b)
	z = a;
else
	z = b;

2.2.3 The switch statement

The C switch allows multiple choice of a selection of items at one level of a conditional where it is a far neater way of writing multiple if statements:

switch( expression)
{
case item1:
	statements1
break;
case item1:
	statements1
break;
...
default:
	statements1
break;
}

In each case the value of item must be a constant, variables are not allowed.

The break is needed if you want to terminate the switch after execution of one choice. Otherwise the next case would get evaluated. Note: This is source for common mistakes (the forgotten break). The default case is optional and catches any other cases.

For example:

switch(  letter)
{
case ' ':
case '\t':
case '\n':
	num_of_spaces++;
	break;
case 'A':
case 'E':
case 'I':
case 'O':
case 'U':
	num_of_vowels++;
	/* note: no break */
default:
	num_of_letters++;
	break;
}

If the value of letter is ' '(space), '\t'(tab) or '\n'(new line character) then num_of_spaces is incremented.
In the above example if the value of letter is 'A', 'E', 'I', 'O' or 'U' then num_of_vowels is incremented.
And for both these vowel values and all other non-space symbols number of num_of_letters is incremented.

2.3 Looping and Iteration

2.3.1 The for statement

The C for statement has the following form:

for( expression1; expression2; expression3)
	statements

expression1 is the initializer; expression2 is the terminate test; expression3 is the modifier (which may be more than just simple increment). NOTE: C basically treats the for statements as the while type loops. For example:

main()
{
	int x;
	for( x = 3; x > 0; x--)
		printf( "x=%d\n", x);
}

...prints:

x=3
x=2
x=1

2.3.2 The while statement

The while statement is similar to those used in other languages although more can be done with the expression statement - a standard feature of C. The while has the form:

while( expression)
	statements

For example (the same program as in the example for the for statement):

main()
{
	int x;

	x = 3;
	while( x > 0)
	{
		printf( "x=%d\n", x);
		x -- ;
	}
}

Because the while loop can accept expressions, not just conditions, the following is legal and furthermore it performs complete operations within the while expression:

while( (ch = getchar()) != 'q')
	putchar( ch);

This example uses C standard library functions getchar() - reads a character from the keyboard - and putchar() - writes a given char to screen. The while loop will proceed to read from the keyboard and echo characters to the screen until a 'q' character is read. NOTE: This type of operation is used a lot in C and not just with character reading!

2.3.3 The do-while statement

C's do-while statement has the form:

do
	statement
while( expression);
For example (again the same program):
main()
{
	int x = 3;

	do {
		printf( "x=%d\n", x);
		x -- ;
	}
	while( x > 0)
}

The main difference between while(){} and do{}while() is: do-while always executes statement at least once and only then it checks the condition.

2.3.4 break and continue

C provides two commands to control how we loop:

- break - exit form loop or switch.
- continue - skip 1 iteration of loop.

Consider the following example where we read in integer values and process them according to the
following conditions. If the value we have read is negative or zero, we wish immediately abandon the loop. If the value read is great than 100, we wish to ignore it and continue to the next value in the data.

while( scanf( "%d", &val) == 1)
{
	if( val <= 0)
		break;	/* abandon the loop */
	if( val > 100)
		continue;	/* skip the val */
	/* some actions with the val */
}

2.4 Arrays

2.4.1 Single and Multi-dimensional Arrays

Let us first look at how we define arrays in C:

int list_of_numbers[50];

BEWARE: In C Array subscripts start at 0 and end one less than the array size. For example, in the above case valid subscripts range from 0 to 49. This is a BIG difference between C and other languages and does require a bit of practice to get in the right frame of mind.

Elements can be accessed in the following ways:-

num3 = list_of_numbers[2];
list_of_numbers[5] = 100;

Multi-dimensional arrays can be defined as follows:

int table_of_numbers[50][50];

for two dimensions. (For further dimensions simply add more [].)
Elements can be accessed in the following ways:

a_number = table_of_numbers[2][3];
table_of_numbers[15][5] = 100;

2.4.2 Strings

In C Strings are defined as arrays of characters. For example, the following defines a string of 50 characters:

char name[50];

C has no string handling facilities built in and so the following are all illegal:

char name[50];
name = "john";

However, there is a special library of string handling routines, e.g. the previous "assignment" may be done so:

char name[50];
strcpy( name, "john");

To print a string we use printf with a special %s control character:

printf("%s", name);

NOTE: We just need to give the name of the string.

In order to allow variable length strings the 0 character is used to indicate the end of a string.

So we if we have a string char name[50]; and we store the "DAVE" in it its contents will look like:

Byte no:
0
1
2
3
4
...
50
Value
'D'
'A'
'V'
'E'
0



2.5 Exercise 1

2.6 Functions

C provides functions which are again similar most languages. One difference is that C regards main() as function. Also unlike some languages, such as Pascal, C does not have procedures - it uses functions to service both requirements.

Let us remind ourselves of the form of a function.

return_type function_name( parameters)
{
	local_variables

	statements
}

Let us look at an example to find the average of two integers:

float average( float a, float b)
{
	float av;
	av = ( a + b) /2.0;
	return( av);
}

We would call the function as follows:

main()
{
	float x = 5, y = 10, z;
	z = average( x, y);
	printf( "average = %f\n", z);
}

Note: The return statement passes the result back to the main program.

2.6.1 void functions

If you do not want to return a value you must use the return type void and miss out the return statement:

void print_first_10()
{
	int i;
	for( i = 1; i <= 10; i ++)
		printf( "%d\n", i);
}

main()
{
	print_first_10();
}

NOTE: We must have () even for no parameters unlike some languages.

2.6.2 Functions and Arrays

Single dimensional arrays can be passed to functions as follows:

float find_max( float list[], int size)
{
	int n;
	float max = list[0];
	for( n = 1;  n < size; n++)
		if( max < list[n])
			max = list[n];
	return( max);
}

Here the declaration float list[]; tells C that list is an array of float. Note we do not specify the dimension of the array when it is a parameter of a function.

Multi-dimensional arrays can be passed to functions as follows:

void print_table( int x_size, int y_size, float table[][5])
{
	int x, y;
	for( x = 0; x < x_size; x ++)
	{
		for( y = 0; y < y_size; y ++)
			printf( "\t%d", table[x][y]);
		printf( "\n");
	}
}

Here float table[][5]; tells C that table is an array of dimension N*5 of float. Note we must specify the second (and subsequent) dimension of the array BUT not the first dimension.

2.6.3 Function Prototyping

Before you use a function C must have knowledge about the type it returns and the parameter types the function expects.

The ANSI standard of C introduced a new (better) way of doing this than previous versions of C. (Note: all new versions of C now adhere to the ANSI standard.)

The importance of prototyping is twofold.


How this is done depends on the scope of the function. Basically if a functions has been defined before it is used (called) then you are OK to merely use the function.

If NOT then you must declare the function. The declaration simply states the type the function returns and the type of parameters used by the function.

It is usual (and therefore good) practice to prototype all functions at the start of the program, although this is not strictly necessary.

To declare a function prototype simply state the type the function returns, the function name and in brackets list the type of parameters in the order they appear in the function definition. E.g.

int strlen(char []);

This states that a function called strlen returns an integer value and accepts a single string as a parameter.

2.7 Further Data Types

2.7.1 Structures

Structures in C allow to define new data type consisting of several other simple data types or structures. For example:

struct gun
{
	char name[50];
	int magazine_size;
	float calibre;
};

struct gun arnies;

defines a new structure gun and makes arnies an instance of it.

NOTE: that gun is a tag for the structure that serves as shorthand for future declarations. We now only need to say struct gun and the body of the structure is implied as we do to make the arnies variable. The tag is optional.

Variables can also be declared between the } and ; of a struct declaration, i.e.:

struct gun
{
	char name[50];
	int magazine_size;
	float calibre;
} arnies;

struct's can be pre-initialised at declaration:

struct gun arnies = { "Kalashnikov", 25, 7.62};

which gives arnies a 7.62mm Kalashnikov with 25 rounds of ammunition.

To access a member (or field) of a struct, C provides the . operator. For example, to give arnie more rounds of ammunition:

arnies.magazineSize=100;

2.7.2 Defining New Data Types

typedef can also be used with structures. The following creates a new type agun which is of type struct gun and can be initialised as usual:

typedef struct gun
{
	char name[50];
	int magazine_size;
	float calibre;
} agun;

agun arnies = { "Kalashnikov", 25, 7.62};

Here gun still acts as a tag to the struct and is optional. Indeed since we have defined a new data type it is not really of much use. agun is the new data type. arnies is a variable of type agun which is a structure. C also allows arrays of structures:

agun arniesguns[1000];
This gives arniesguns a 1000 guns. This may be used in the following way:

arniesguns[51].calibre = 5.45;

gives Arnie's gun number 50 a calibre of, and:

itscalibre = arniesguns[0].calibre;

assigns the calibre of Arnie's first gun to itscalibre.

2.7.3 Unions

A union is a variable which may hold (at different times) objects of different sizes and types. C uses the union statement to create unions, for example:

union number
{
	short shortnumber;
	long longnumber;
	float floatnumber;
} anumber;

defines a union called number and an instance of it called anumber. number is a union tag and acts in the same way as a tag for a structure.

Members can be accessed in the following way:

printf( "%d\n", anumber.longnumber);

This clearly displays the value of longnumber.

When the C compiler is allocating memory for unions it will always reserve enough room for the largest member (in the above example this is 8 bytes for the double).

2.7.4 Coercion or Type-Casting

C is one of the few languages to allow coercion, that is forcing one variable of one type to be another type.
C allows this using the cast operator (). So:

int int_val;
float float_val = 9.87;

int_val = (int)float_val;

assigns 9 (the fractional part is thrown away) to int_val. And:

int int_val = 10;
float float_val;

float_val = (float)int_val;

assigns 10.0 to float_val.

When cast is necessary? E.g. to get really float result of division:

int int_val1 = 10;
int int_val2 = 4;
float float_val;

float_val = (float)int_val1 / (float)int_val2;
This ensures floating point division.

2.7.5 Enumerated Types

Enumerated types contain a list of constants that can be addressed in integer values. We can declare types and variables as follows:

enum days {mon, tues, ..., sun}week;

enum days week1, week2;

NOTE: As with arrays first enumerated name has index value 0. So mon has value 0, tues 1, and so on.
The week1 and week2 are variables.

We can define other values:
enum spec_chars { new_line = '\n', tab = '\t', backspace = '\b'};

We can also override the 0 start value:

enum months {jan= 1, feb, mar, ......, dec};

Here it is implied that feb = 2 etc.

2.7.6 Static Variables

A static variable is local to particular function. However, it is only initialized once (on the first call to function).
Also the value of the variable on leaving the function remains intact. On the next call to the function the static variable has the same value as on the last leaving.

To define a static variable simply prefix the variable declaration with the static keyword. For example:

void stat_test()
{
	int local_var = 0;
	static int static_var = 0;

	printf( "local = %d, static = %d\n", local_var, static_var);
	local_var ++ ;
	static_var ++ ;
}

main()
{
	int n;

	for( n = 0; n < 3; n++)
		stat_test();
}

Output is:

local = 0, static = 0
local = 0, static = 1
local = 0, static = 2

Clearly the auto_var variable is created each time. The static_var is created once and remembers its value.

2.8 Common Mistakes in C

2.8.1 Assignment (=) versus Comparison (==)

C uses assignment by value so

if (a = b) ...

is legal. Note: b is assigned to a, the expression a = b results with the new value of a, so the if is equivalent to:

a = b;
if( b) ...

I.e. it is true if b != 0, false if b == 0.

2.8.2 Missing () of a function

It is common to forget to put () at the end of a function The () must be placed EVEN if there are no parameters passed to the function.

2.8.3 Array indices

An array of n elements has a

0..n-1 index range. (NOT 1-n range!!!)

2.8.4 C is Case Sensitive

The upper/lower case chars are regarded as being different in C. The variables my_var and My_var are completely different variables!

2.8.5 Semicolons end every statement

This is easy to forget but the compiler will pick this up (usually this is just a syntax error).

2.9 Pointers

Pointers are a fundamental part of C. If you cannot use pointers properly then you have basically lost all the power and flexibility that C allows. The secret to C is in its use of pointers.
C uses pointers a lot. Why?

C uses pointers explicitly with:

2.9.1 What is a Pointer?

A pointer is a variable containing the address in memory of another variable. We can have a pointer to any variable type.

The unary operator & gives the "address of a variable".
The indirection (or dereference) operator * gives the "contents of an object pointed to by a pointer".
To declare a pointer to a variable do:

int *pointer;

NOTE: We must associate a pointer to a particular type: You can't assign the address of a short int to a long int, for instance (although it is possible to do with casting).

Consider the effect of the following code:

int x = 1, y = 2;
int* ip;

ip = &x;
y = *ip;
*ip = 3;

Assume for the sake of this discussion that variable x resides at memory location 100, y at 200 and ip at 1000. Note: a pointer is a variable and thus its values need to be stored somewhere.

Now the assignments x = 1 and y = 2 obviously load these values into the variables. ip is declared to be a pointer to an integer and is assigned to the address of x (&x). So ip gets loaded with the value 200.

Next y gets assigned to the contents of ip. In this example ip currently points to memory location 200 - the location of x. So y gets assigned to the values of x - which is 1.

Finally we can assign a value to the contents of a pointer (ip).

IMPORTANT: When a pointer is declared it does not point anywhere. You must set it to point somewhere before you use it.

So the source:

int* ip;
*ip = 100;

will generate an error (usually program crash). The correct use might be:

int* ip;
int x;

ip = &x;	/* assign to ip some REAL memory address */
*ip = 100;

We can do integer arithmetic on a pointer (so called pointer arithmetic):

int *ip;
...
ip += 10;
++ip;

The reason we associate a pointer to a data type is so that it knows how many bytes the data is stored in. When we increment a pointer we increase the pointer by one "block" memory.

So for a character pointer ++ch_ptr adds 1 byte (sizeof( char) == 1) to the address. For an integer or float ++ip or ++flp adds 4 bytes (sizeof( int) == 4 and sizeof( float) == 4) to the address. So the code:

float* fp;
float arr[100];

fp = arr;	/* fp points to the beginning of arr, i.e. to arr[0] */
fp += 2;	/* fp points to arr[2] */

In the statement fp += 2; the pointer fp moves 2 float posisitons, i.e. 8 bytes ( 2 * sizeof( float) == 2 * 4).

2.9.2 Pointer and Functions

Let us now examine the close relationship between pointers and C's other major parts. We will start with functions.

When C passes arguments to functions it passes them by value.

There are many cases when we may want to alter a passed argument in the function and receive the new value back once to function has finished. C uses pointers explicitly to do this. (Other languages mask the fact that pointers also underpin the implementation of this.)

The best way to study this is to look at an example where we must be able to receive changed parameters.

Let us try and write a function to swap variables around?

The usual function call:

swap( a, b);	/* WON'T WORK */

Pointers provide the solution: Pass the address of the variables to the functions and access address of function.

Thus our function call in our program would look like this:

swap( &a, &b);

The code to swap is fairly straightforward:

void swap( int* px, int* py)
{
	int temp;

	temp = *px;
	*px = *py;
	*py = temp;
}

2.9.3 Pointers and Arrays

Pointers and arrays are very closely linked in C.
(Hint: think of array elements arranged in consecutive memory locations.)

Consider the following:

int a[10], x;
int* pa;

pa = &a[0];		/* pa points to address of a[0] */
x = *pa;		/* the same: x = a[0] */

To get somewhere in the array using a pointer we could do:
pa + i  <------>  a[i]

WARNING: There is no bound checking of arrays and pointers so you can easily go beyond array memory and overwrite other things.

C however is much more subtle in its link between arrays and pointers.

For example we can just type

pa = a;
instead of

pa = &a[0];

and

a[i]
can be written

as *(a + i)

i.e. &a[i] <-------> a + i, i.e. name of an array is also a pointer to the beginning of the array in the memory.

We also express pointer addressing like this:

pa[i]  <------->  *(pa + i)
However pointers and arrays are different:


This stuff is very important. Make sure you understand it.

We can now understand how arrays are passed to functions. When an array is passed to a function what is actually passed is its initial elements location in memory.

So:

strlen(s)  <------->  strlen(&s[0])

This is why we declare the function:

int strlen( char s[]);

An equivalent declaration is:

int strlen(char *s);

since char s[] <-------> char *s.

strlen() is a standard library function that returns the length of a string. Let's look at how we may write a function:

int strlen( char* s)
{
	int i;
	for( i = 0; s[i] != '\0'; i++)
		;
	return( i);
}

Now lets write a function to copy a string to another string. (strcpy() is a standard library function that does this.)

void strcpy( char* d, char* s)
{
	whle( ( *d++ = *s++) != '\0')
		;	/* NOTE: Uses of empty statements */
}

This uses pointers and assignment by value.

2.9.4 Arrays of Pointers

We can have arrays of pointers since pointers are variables. Let's take an example: sort lines of text of different length. (NOTE: Text can't be moved or compared in a single operation.)

Arrays of pointers are a data representation that will cope efficiently and conveniently with variable length text lines.

How can we do this?:




This eliminates:

2.9.5 Multidimensional arrays and pointers

In C: a 2D array is really a 1D array, each of whose elements is itself an array. Hence:

a[n][m] notation.

Array elements are stored row by row.

When we pass a 2D array to a function we must specify the number of columns - the number of rows is irrelevant.

The reason for this is pointers again. C needs to know how many columns in order that it can jump from row to row in memory.

Considering a[5][35] to be passed in a function:

We can do:

f(int a[][35]) {.....}

Now lets look at the (subtle) difference between pointers and arrays. Strings are a common application of this. Consider:

char* ptr_name[10];
char arr_names[10][20];

We can legally do ptr_name[3][4] and arr_name[3][4] in C. However:

The disadvantages of the latter are:
1. there are extra memory units used for pointers;
2. the programmer must worry to supply the necessary memory for the strings themselves (usually via dynamic memory allocation - will be discussed later).
The advantage is that each pointer can point to arrays be of different length.

2.9.6 Static Initialization of Pointer Arrays

Initialization of arrays of pointers is an ideal application for an internal static array.

void some_func()
{
	static char* months[] =
	{
		"jan",
		"feb",
		...
	};
}

2.9.7 Pointers and Structures

These are fairly straight forward and are easily defined. Consider the following:

struct COORD
{
	float x, y, z;
};
struct COORD pt;
struct COORD* pt_ptr;

pt_ptr = &pt;

The -> operator lets us access a member of the structure pointed to by a pointer, i.e.:

pt_ptr->x = 1.0;
pt_ptr->y = pt_ptr->y - 3.0;

(Recall the examples using . operator allowing access to a member of the structure when there is no use of pointers.)

Let's take a very important example: a trivial linked list of integers. It means that we want to store many integers somewhere but we don't know how many integers there will be.


Let's do it:

typedef struct element_struct
{
	int			val;
	struct element_struct*	next;
} NODE;

Now we can link several elements. In a C program this may look:

NODE n1, n2, n3;
n1.next = &n2;
n2.next = &n3;
...

(We don't want to discuss how to create new elements and how to manage such a list, e.g. what is the sign of the end - all this will be discussed later.)

Note an important thing: we can only declare next as a pointer to NODE. We cannot have a element of the variable type as this would set up a recursive definition which is NOT ALLOWED. We are allowed to set a pointer reference since 4 bytes are set aside for any pointer.

The above code links a node n1 to n2, etc. We will look at this matter further later.

2.9.8 Common Pointer Pitfalls

Here we will highlight two most common mistakes made with pointers.

2.9.8.1 Not assigning a pointer to memory address before using it

int* x;
*x = 100;

The error here is that we assign some value (100) to not initialized memory. We need a physical memory location, e.g.:
int y;
int* x;
x = &y;
*x = 100;

This error may be hard to spot. NO COMPILER ERROR. Also x could some random address so we can corrupt another memory field.

2.9.8.2 Illegal indirection

int y;
int* x;
x = &y;
x = 100;

The error here is that we assign some value (100) to the pointer itself not in the memory it points to. So instead to set y to 100 (as we wanted) we assigned 100 to the pointer (and due to such a value is usually an invalid to address a memory we will get run-time error next time we try to obtain value from the *x.)

2.10 Dynamic Memory Allocation

Dynamic allocation is a pretty unique feature to C (amongst high level languages). It enables us to create data types and structures of any size and length to suit our programs need within the program.

We will look at two common applications of this:

2.10.1 Malloc

The function malloc is most commonly used to attempt to "grab" a portion of memory. It is defined by:

void* malloc( unsigned int number_of_bytes);

That is to say it returns a pointer to a character that is the start in memory of the reserved portion of size number_of_bytes. If memory cannot be allocated a NULL pointer is returned (a good C program must check this).

So:

char* cp;
cp = (char*) malloc( 100);	/* to get 100 bytes and assign the */
/* start address to cp */

If you want a pointer to another data type you must use coercion. Also it is usual to use the standard built-in function sizeof() to specify the number of bytes:

int* ip;
ip = (int*)malloc( 100 * sizeof( int));

The (int *) means coercion to an integer pointer. Coercion to the correct pointer type is very important to ensure pointer arithmetic is performed correctly.

It is good practice to use sizeof() even if you know the actual size you want - it makes for platform independent (portable) code.

sizeof() can be used to find the size of any data type, variable or structure. Simply supply one of these as an argument to the function.

So:

int i;
struct COORD { float x, y, z; };
typedef struct COORD PT;
PT pt;
PT* pt_ptr;

All the following:

sizeof( int)
sizeof( i)
sizeof( struct COORD)
sizeof( PT)
sizeof( pt)
sizeof( pt_ptr)

is acceptable and completely legal.

Like above we can treat the reserved (allocated) memory like an array. i.e. we can do things like:

int* ip;
ip = (int*)malloc( 100 * sizeof( int));
for( i = 0; i < 100; i++)
scanf( "%d", ip[i]);

When we finish to use the allocated memory we should "return" it using another standard function:

void free( void* ptr);

2.10.2 Linked Lists

Let us now return to out linked list example:

typedef struct element_struct
{
	int			val;
	struct element_struct*	next;
} NODE;

Let's define that the sign of the last element of the list is NULL in its "next" field. So we can write, for example, a procedure to print the list (the function printf() used here will be discussed in more details later):

void print_list( NODE* list)
{
	NODE* node;
	for( node = list; node != NULL; node = node->next)
		printf("%d\n", node->val);
}

We can now try to grow the list dynamically:

new_node = (NODE*) malloc( sizeof( NODE));

This will allocate memory for a new element. Well, now we can store as many elements as we need, e.g.:

int main()
{
	NODE* first;
	NODE* node;
	NODE* new_node;

	first = NULL;

	while( something)
	{
		new_node = (NODE*) malloc( sizeof( NODE));
		new_node->val = some_value;
		new_node->next = NULL;
		/* if our list is empty - that's all */
		if( first == NULL)
		{
			first = new_node;
			continue;
		}
		/* let's find the last element of the list */
		for( node = first; node->next != NULL; node = node->next)
			;
		node->next = new_node;
	}
	/* Now we can use the newly built list, e.g. to print it
	   using the procedure written above */
	print_list( first);
}

Do not forget to free the memory not needed anymore with the free() function.

2.11 Input and Output (I/O)

Now we will look at many forms of I/O. We have briefly mentioned some forms before.

Firstly your programs will need to include the standard I/O header file so do:
#include <stdio.h>

2.11.1 Streams

Streams are a portable way of reading and writing data. They provide a flexible and efficient means of I/O.

A stream may be a file or a physical or virtual device (e.g. printer, monitor or window) which is manipulated with a handler of the stream (usually implemented as a pointer).

The most important and frequently used streams in C are represented by structure FILE (which is defined in stdio.h). We simply need to refer to the FILE structure in C programs when performing I/O with streams. Usually FILE is used to deal with real files, keyboard input and terminal output (that may be redirected but these are details of a specific operating system, e.g. MS Windows).

To use a stream we need:
  1. to declare a variable or pointer of this type in our programs (we do not need to know any more specifics about this definition),
  2. to open a stream before doing any I/O,
  3. then access it,
  4. and then close it.

2.11.1.1 Predefined Streams

Most operating systems define 3 predefined streams (in stdio.h):
They all use text as the method of I/O. These streams are automatically open in any program.

2.11.1.2 Redirection

By default the standard input stream accepts input from the keyboard while the standard output and standard error streams prints to the screen (terminal, window, etc.). The most of operating systems allow to override the predefined I/O defaults.

This is not part of C but operating system dependent. We will do redirection from the command line.

> - redirect stdout to a file.

So if we have a program, out, that usually prints to the screen then

out > file1

will send the output to a file, file1.

< - redirect stdin from a file to a program.

So if we are expecting input from the keyboard for a program, in we can read similar input from a file

in < file2

| - pipe: puts stdout from one program to stdin of another

prog1 | prog2

e.g. to sent output (usually to console) of a program to another program performing text formatting:

prog | fmt

2.11.2 Basic I/O

There are a couple of function that provide basic I/O facilities. Probably the most common are: getchar() and putchar(). They are defined and used as follows:

int getchar(void) - reads a char from stdin.
int putchar(int ch) - writes a char to stdout, returns character written.

E.g.:

int ch;
ch = getchar();
putchar( ch);
Related Functions:

int getc(FILE *stream)
int putc(int ch,FILE *stream)

2.11.3 Formatted I/O

We have seen examples of how C uses formatted I/O already. Let's look at this in more detail.

2.11.3.1 printf

The function is defined as follows:

int printf(char *format, arg list ...) - prints to stdout the list of arguments according specified format string. Returns number of characters printed.

The format string has 2 types of object:


Format spec (%)
Type
Result
c
char
single character
i, d
int
decimal number
o
int
octal number
x, X
int
hexadecimal number
u
int
unsigned int
s
char*
print string terminated by '\0'
f
double, float
format: m.ddd...
e,E
double, float
scientific format: -5.46e003
g, G
double, float
the most compact of f or e formats
%
-
print % character

Between % and format char we can put:

- (minus sign) - left justify.
integer number - field width.
m.d - m = field width, d = precision of number of digits after decimal point or number of chars from a string.

So:

printf( "%-6.3f", 15.9826)

The output on the screen is:

15.983

and:

printf( "my part is 15.5%%\n");

...outputs:

my part is 15.5%

2.11.3.2 scanf

This function is defined as follows:

int scanf(char *format, args....) - reads from stdin and puts input in address of variables specified in args list. Returns number of chars read.

Format control string similar to printf().

Note: The ADDRESS of variable or a pointer to one is required by scanf.

scanf("%d'', &i);

We can just give the name of an array or string to scanf since this corresponds to the start address of the array/string.
char line[80];
scanf( "%s", line);

2.11.4 Files

Files are the most common form of a stream.

The first thing we must do is open a file. The function fopen() does this:

FILE *fopen(char *name, char *mode)

fopen() returns a pointer to a FILE. The name string is the name of the file on disc that we wish to access. The mode string controls our type of access. If a file cannot be accessed for any reason a NULL pointer is returned.

Modes include:
"r" - read
"w" - write
"a" - append

To open a file we must have a pointer that points to a FILE structure. So to open a file, called myfile.dat for reading we would do:

FILE* fp;
fp = fopen( "myfile.dat", "r");

It is good practice to check if file has been opened OK:

FILE* fp;
if( ( fp = fopen( "myfile.dat", "r")) == NULL)
{
	printf( "Can't open file myfile.dat\n");
	return( some_err_code);
}
... read the file ...

2.11.4.1 Reading and writing FILES

The functions fprintf and fscanf a commonly used to access files.

int fprintf( FILE* stream, char* format, args...);
int fscanf( FILE* stream, char* format, args...);

These are similar to printf() and scanf() except that data is read from the stream that must have been opened with fopen().

The stream pointer is automatically incremented with ALL file read/write functions. We do not have to worry about doing this.

FILE* fp;
char line[80];
if( ( fp = fopen( "myfile.dat", "r")) == NULL)
{
	printf( "Can't open file myfile.dat\n");
	return( some_err_code);
}
fscanf( fp, "%s", line);

Other functions for files:

int getc(FILE *stream);
int fgetc(FILE *stream);
int putc(char ch, FILE *s);
int fputc(char ch, FILE *s);

These are like getchar, putchar.

NOTE: getc is defined as preprocessor MACRO in stdio.h. fgetc is a C library function. Both achieve the same result.

fflush(FILE *stream) - flushes a stream.
fclose(FILE *stream) - closes a stream.

Of course, we can access predefined streams with fprintf etc.

fprintf(stderr,´´Cannot Compute!!n'');
fscanf(stdin,´´%s'',string);

2.11.5 sprintf and sscanf

These are like fprintf and fscanf except they read/write to a string.

int sprintf(char *string, char *format, args..);
int sscanf(char *string, char *format, args..);

For example:

float Pi=3.141.59;
char line_for_output[80];
sprintf( line_for_output, "PI=%f", Pi);

2.11.6 Command line input

We can type arguments after the program name when we run the program. C lets read arguments from the command line which can then be used in our programs.

We have seen this with the some editor for example:

winword MyDoc.doc

winword is the program, MyDoc.doc the argument.

In order to be able to use such arguments in our code we must define them as follows:

main(int argc, char **argv)

So our main function now has its own arguments. Here:

A simple program example:

main( int argc, char* argv[])
{
	int i;
	for( i = 0; i < argc; i++ )
		printf( "%d = %s\n", i, argv[i]);
}

Assume it is compiled to run it as argv. So if we type:

argv abc def xyz

The output would be:

0 = argv
1 = abc
2 = def
3 = xyz

2.12 Low Level Operators

We will not discuss such things detailed as we don't need them for the purposes of this course. Anyway it is necessary to know their existence, so we will pass them briefly. (More details may be found in on-line help and reference manuals.)

2.12.1 Bitwise Operators

The bitwise operators of C a summarized in the following table:

&
AND
|
OR
^
XOR (exclusive OR)
~ (unary)
One's Complement (0->1; 1->0)
<<
Left shift
>>
Right shift

NOTE: DO NOT confuse & with &&: & is bitwise AND, && is logical AND. Similarly for | and ||.

2.12.2 Bit Fields

Bit Fields allow the packing of data in a structure. This is especially useful when memory or data storage is at a premium (not the case of our course's programming). Typical examples:

C lets us do this in a structure definition by putting :bit length after the variable. e.g.:

struct packed_struct
{
	unsigned int flag1:1;
	unsigned int flag2:1;
	unsigned int flag3:1;
	...
};

NOTE:

2.13 The C Preprocessor


The preprocessor more or less provides its own language which can be a very powerful tool to the programmer. All preprocessor directives or commands begin with a #.

Use of the preprocessor is advantageous since it makes:

2.13.1 #define

Use this to define constants or any macro substitution. Use as follows:

#define macro replacement

For example:

#define PI	3.14159

We can also define small "functions'' using #define. For example max. of two variables:

#define max(A,B)	((A)>(B)?(A):(B))

(Recall that ? is the ternary operator in C.)
Note: that this does not define a proper function max(). All it means that wherever we place max(x,y) the text gets replaced by the appropriate definition. (x and y are any variable names - not necessarily x and y.)

So if we have:

int x = 8;
int y;
y = max(x, 10);

This is preprocessed to:

int x = 8;
int y;
y = ((x)>10?x:10);

BE CAREFUL: if in our C code we typed something like:

x = max( q + r, s + t);

after preprocessing, if we were able to look at the code it would appear like this:

x = ( ( q + r) > ( s + t) ? ( q + r) : ( s + t));

So we pay by calculations performed twice. Moreover if we write:

x = max( a++, b++);

then we get:

x = ( (a++) > (b++) ? (a++) : (b++));

So we change value of either a or b (whichever is greater) twice!

Other examples of #define could be:

#define percents( x, y)	((x)*100./(y))

2.13.2 #undef

This commands undefined a macro. (A macro must be undefined before being redefined to a different value.)

2.13.3 #include

This directive includes a file into code.

It has two possible forms:

#include <file>

or

#include "file"

<file> tells the compiler to look first in the directories specified by user and then where system include files are held (this place is compiler dependent).
"file" looks for a file first in the current directory and then in the directories specified by user.

NOTE: different compilers treat "the current directory" differently, so it is not recommended to use the latter notation.

Included files usually contain C prototypes and declarations from header files and not (algorithmic) C code.

2.13.4 #if - Conditional inclusion

#if evaluates a constant integer expression. You always need a #endif to delimit end of statement.

We can have else etc. as well by using #else. Another common use of #if is with:

#ifdef - if defined
#ifndef - if not defined

These are useful for checking if macros are set - perhaps from different program modules and header files.
Usually this is used to write portable code (i.e. program that may be compiled by different compilers on different platforms) - using macros predefined by compilers.

For example, let's assume that you want to handle as big integer number as possible. MS C++ has a special built-in type __int64 allowing to store very big numbers (up to about 4e18). So you can write:

#ifdef _MSC_VER
typedef __int64	BIG_INT;
#else
typedef long	BIG_INT;
#endif

BIG_INT	my_big_integer;

(Note that sizeof( BIG_INT) will tell you the real size of the type if you need it.)

2.14 ANSI C Library

2.15 Exercise 2