You are on page 1of 26

Computer Science

Aston University
Birmingham B4 7ET
http://www.cs.aston.ac.uk/

C Programming for Computer Graphics


Dan Cornford (from Ian T. Nabney)

CS2150 September 29, 2004

1 *Introduction*

This set of notes covers the basic material you will need to understand C to a sufficient level to be able to
program well enough to implement some quite complex graphics. These are notes written by Ian Nabney, which
I have left pretty much intact, for the sake of completeness. Only those sections marked with a * are
dirrectly relevant to the graphics course, the rest are for your information.

Why should we use a general purpose programming language like C? There are several reasons:

• speed of computation;
• control over calculation;
• ease of integration;
• expense.

What is the C language? The most widespread system and application programming language? The most
invasive computer virus in the world? The language of choice for speed and flexibility? Like hieroglyphics, the
last attempt by the priest-hood to deny access to the computer? Probably all of these things.

Many of the most important ideas in C stem from BCPL (British Computer Programming Language), developed
by Martin Richards at Cambridge University. The influence came about indirectly, through the programming
language B written by Ken Thompson in 1970 for the first Unix system.

C was originally designed as a systems programming language (i.e. to write operating systems, compilers etc.)
that would enable porting of operating systems to new computer architectures to be an easier process: only a
small amount of code in Unix has to be written in assembler. Because of this, C can be likened to a high level
assembly language in that, within the framework of a 3rd generation language, you can manipulate information
down to the level of a byte or even individual bits. These may be manipulated with the same sort of operations
provided by most machines. The core of C is quite small. There are few keywords, but the language is very
rich in operators.

Keywords Statements Operators Manual Pages


Pascal 35 9 16 28
Modula-2 40 10 19 25
C 29 13 44 40
C++ 48 14 52 155
Ada 63 17 21 241

Table 1.1: Comparison of complexity of programming languages

Despite the original design aims, C has been successfully used as a general purpose programming language.
The small size of C means that it is relatively easy to write compilers (and hence the language can spread to
2 CS2150 C Programming for Computer Graphics

new architectures quickly), compilers generate efficient code (as C is ‘close’ to assembly languages), and the
language is basically easy to learn. One price for this is that I/O, string handling and even memory allocation
are not part of the C language. C provides no operations that operate on larger, composite objects, such as sets
or arrays. Instead, functions must be called. If you are fortunate, and the function you require belongs to the
standard libraries that C programmers became tired of writing over and over again, then it is simply a matter
of linking to the correct library. Otherwise, you will have to write it yourself.

There are literally hundreds of books on C. The ones that I use (and I make no claim to their being the best)
are:

• The C Programming Language, B. W. Kernighan and D. M. Ritchie, Prentice-Hall.


• C: The Complete Reference, H. Schildt, McGraw-Hill.
CS2150 C Programming for Computer Graphics 3

Contents
1 *Introduction* 1
2 *Getting Started* 4
3 *Variables* 4
3.1 *Names* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
3.2 *Types and Constants* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
3.3 *Declarations* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
3.4 *Enumerated Types* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
3.5 *Constant Variables* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
4 *Operators and Expressions* 6
4.1 *Arithmetic Operators* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
4.2 *Relational and Logical Operators* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
4.3 Bitwise Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
4.4 Conditional Operator. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
4.5 *Assignment Operators* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
4.6 *Type Conversions* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
4.7 *Operator Precedence*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
5 *Statements* 10
5.1 *Compound Statements* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
5.2 *Expressions* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
5.3 *Conditional Statements* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
5.4 *Switch Statements* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
5.5 *Loops* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
5.6 *Control Flow Modifiers* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
6 *Functions* 14
6.1 *Syntax* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
6.2 *Arguments, Local Variables and Return Values* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
6.3 Recursion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
7 *Pointers and Arrays* 16
7.1 *Pointer Variables* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
7.2 *Arrays* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
7.3 Strings and Character Pointers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
7.4 Memory Allocation and Dynamic Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
7.5 Matrices and Arrays of Pointers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
7.6 Pointers to Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
8 Structures and Object-Based Design 22
8.1 Structure Definition and Element Access . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
8.2 Structure Pointers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
8.3 Modularisation and Scope . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
8.3.1 Include Files for Declarations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
8.3.2 *The static Keyword* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
9 *Summary* 26
4 CS2150 C Programming for Computer Graphics

2 *Getting Started*

The first program to write is the same for all languages: “Hello world”. The way to write this in C is:

#include <stdio.h>
main()
{
/* This is a comment */
printf("Hello world\n");
}

The include line ensures that the declarations of functions used in I/O are available to the compiler. Every
C program must contain a main function: this is where execution starts. Curly braces { and } are used to
enclose a program block or, as in this case, a function definition. All statements are terminated by a semi-colon.
Comments are written between the string pairs /* and */.

The fifth line is a function call to printf with the single argument given by the string constant "Hello world\n".
This is a library function that prints arguments on standard output – usually your computer screen. The \n
sequence is C notation for the newline character that advances the terminal to the left margin on the next line.
Note that C is basically a lower case language; all the keywords are lower case.

3 *Variables*

3.1 *Names*

Variable names are made up of letters, numbers and the underscore character. The first character must be
a letter. The number of significant characters is reasonably large, but not infinite. Not all compilers will
distinguish between

my_temporary_internal_variable_1

and

my_temporary_internal_variable_2

In any case, the number of significant characters can vary from compiler to compiler. If you want to move
code from machine to machine (or ‘port’ it), the basic rule is “Don’t push your luck.” Keep your programming
simple and well within the bounds of the standards.

3.2 *Types and Constants*

There are four basic types in C: char for single characters, int for integers, float for floating point numbers
and double for double precision floating point numbers.

In addition, there are four type modifiers: signed, unsigned, short and long.

For signed values the high-order bit is interpreted as a sign flag (usually with two’s complement representation),
while unsigned values interpret the high-order bit as 2n−1 , where n denotes the number of bits in the value.
Note that char values are unsigned by default, while int values are signed by default.

The modifiers short and long may affect the amount of storage used for a value. They can be applied to int
values, and long can be applied to a double. There are no standard sizes laid down, but a short int is no
longer than an int, which is no longer than a long int.
CS2150 C Programming for Computer Graphics 5

Numeric constants are written in the usual way. Scientific notation may also be used:

0.12e7 5.64E-6

A leading zero in an integer constant means that the following digits are treated as an octal number. Similarly,
a leading 0x or 0X indicates hexadecimal.

It is good practice to use symbolic constants for all but the most obvious values in a program. These are best
defined at the head of the file (outside the function definitions) as in the following example:

#define GOLD 1.618034

This symbolic name can then be used wherever the numeric constant may be. The value is evaluated at compile
time.

c = a + (b-a) * GOLD;

A character constant is written as a single character enclosed in single quotes, such as ’x’. Special characters,
such as ’\n’ and ’\t’ are used for non-alphanumeric characters such as formatting characters. (These represent
a new line and a tab respectively.)

A string constant is a sequence of characters enclosed in double quotes:

"Hello sky. Hello trees."

A string is an array whose elements are single characters. The array length is one greater than the number of
characters, as the null character ’\0’ is placed in an additional storage location at the end of the string.

3.3 *Declarations*

All variables should be declared before use. The preferred style for declarations is

type variable_name = initial_value; /* Description */


int num_good_searches = 0; /* Number of successful line searches */

Most variables (more detail will be given later when we cover functions and scoping) have undefined values
without initialisation; this means that they may contain whatever rubbish the compiler decides to put into that
location.

On large software projects, the rule for declarations is “No raw types”. This means that instead of using a
built-in type, a type name should be created to describe the logical rather than physical type of a variable. This
is done with a typedef statement.

typedef type type_name;


typedef float Real;

Once the new type name has been defined, it can be used in variable declarations. One advantage of this is
that the meaning of the program is made clearer. Another advantage is that if it becomes necessary to modify
the machine type, it can be done by changing the typedef statement, rather than every variable declaration.
6 CS2150 C Programming for Computer Graphics

3.4 *Enumerated Types*

An enumeration is a set of named integer constants that specify all the legal values that a variable of that type
may have. These labels can then be used in place of numeric constants. The machine type of an enumerated
type is an int. The syntax is

enum tag {enumeration_list} variable_list;

where both the tag (which names the type) and the variable list (which declares some variables of this type)
are optional. The style I would recommend is to name the type, but to declare variables in separate statements.

An example is given by different types of variables in datasets, which typically have to be treated in different
ways by programs. Discrete variables have a finite set of possible values, ordinal variables have a finite set
of values that are ordered, and continuous variables can take any real value (perhaps restricted to a certain
interval). A program that manipulates data would therefore have a use for an enumerated type declared as
follows:

enum VarType {discrete, ordinal, continuous};

3.5 *Constant Variables*

The title of this section may look like an oxymoron. A variable of type const may not be changed during
execution of a program: it can be given an initial value. The const modifier can be applied to a variable of any
type, and is usually to be preferred to the use of constants introduced with #define statements as in 3.2. This
is because the compiler can then check that the type of the value is suitable for its context. The syntax is very
simple:

const type variable = value;

So we can replace

#define GOLD 1.618034

by

const float GOLD = 1.618034;

4 *Operators and Expressions*

An expression is a language construct that can be evaluated to a single value. It is made up of values acted on
by operators. C’s large set of operators can be grouped in 5 classes: arithmetic, relational, bitwise, conditional,
and assignment.

4.1 *Arithmetic Operators*

The binary arithmetic operators are +, -, *, / and %. There is also a unary minus operator -. These operators
all act in the expected way. When division is used with integer arguments, the fractional part of the answer is
truncated (so the value is rounded towards zero). The modulus operator % can only be applied to integers and
returns the remainder from integer division. Hence the expression
CS2150 C Programming for Computer Graphics 7

(x/y)*y + x%y

should always evaluate to x for integers x and y.

Note that there is no exponentiation operator: instead the library function pow must be used.

4.2 *Relational and Logical Operators*

These operators all generate Boolean results. Boolean values are 1 for true and 0 for false, and are stored as
int values.

The comparison operators are >, >=, <, <=, ==, and !=. The last two represent equality and inequality respec-
tively. Be warned that a single equals sign, =, will have a very different effect.

The logical connectives && and || are unique among operators in that they can force early termination of
expression evaluation. Expressions joined by these operators are evaluated in strict left to right order, and
evaluation stops as soon as the truth of falsehood of the result is determined. The unary logical negation
operator ! converts a non-zero operand to zero and a zero operand to 1.

In fact any integer value can be tested for truth or falsehood, and in such circumstances, any non-zero value
is treated as true and 0 is false. This often leads to counter-intuitive code when testing return values from
a function, as there are usually several different error conditions (i.e. false values) and only value needed to
represent correct termination. A good example of this is the library function

strcmp(char *s1, char *s2)

which returns an integer representing the first character where s1 and s2 differ, and 0 if they are the same.
Thus to test if two strings are identical, we use the expression

!strcmp(s1, s2)

4.3 Bitwise Operators

C provides six operators that operate on individual bits. It is safest to use these operators on unsigned quantities,
as otherwise the treatment of the sign bit can be machine dependent (and hence unpredictable). This applies
particularly to the right shift operator. These operators cannot be applied to float or double values. These

& bitwise logical AND


| bitwise logical OR
^ bitwise logical exclusive OR
<< left shift
>> right shift
~ one’s complement (unary operator)

Table 4.1: Bitwise Operators

are all self-explanatory with the exception of the shift operators. These shift the left operand by the number
of positions given by the right operand. Vacated bit positions are filled with zeros. Thus, if x is an unsigned
integer,

x << 2;

is equivalent to multiplication by 4.
8 CS2150 C Programming for Computer Graphics

4.4 Conditional Operator

This operator is one of C’s oddities. It provides a compact form with which to express certain ‘if then else’
control structures. In the expression

e1 ? e2 : e3

e1 is evaluated. If it is true (non-zero), then e2 is evaluated and that is the value of the expression. Otherwise
e3 is evaluated, and that is the value. This operator is rarely used: the most common occurrence is to calculate
the maximum (or minimum) of two values:

(a > b) ? a : b

4.5 *Assignment Operators*

One unusual, and to the language theorist upsetting, feature of C is the use of assignment operators in expres-
sions. It has the advantages of making it easier for the compiler to generate efficient code and making the code
more compact, but the drawback is that even expressions that do not evaluate functions can have side effects
(i.e. change the values stored in variables). The result of assignment is the assigned value:

i = 2

is an expression which assigns 2 to the variable i and has the value 2 as well.

Most binary operators op have a corresponding assignment operator of the form op=. So

e1 op= e2

is equivalent to

e1 = (e1) op (e2)

So

i += 2

is equivalent to

i = i + 2

The following operators do have an assignment version.

+ - * / % << >> & ^ |

An even more specialised group of operators that are extremely useful are the increment and decrement oper-
ators. These add (or subtract) one from the value they operate on. They can be placed before the variable
++n or after n++. The expression ++n increments n before using its value, while n++ increments n after using its
value. If n = 5 then

x = n++;
CS2150 C Programming for Computer Graphics 9

sets x to 5, while

x = ++n;

sets x to 6. In each case n has the value 6 after execution. This construct is commonly used in loop counters
and to iterate over the elements of an array.

One final operator that is included in this section mainly since it only has any purpose for expressions with side
effects is the comma operator. A pair of expressions separated by a comma is evaluated left to right with the
result being the value of the right expression. Since the value of the left expression is ignored, this makes no
sense unless it is an assignment operator. We shall see an example of the comma operator with the for loop.

4.6 *Type Conversions*

When operands of different types appear in expressions they are converted to a common type according to a
set of simple rules. Expressions that don’t make sense, such as using a float as an array index, are disallowed.
The C compiler converts all operands up to the largest type of any operand, by a process called type promotion.

The rules for arithmetic operators are:

If an operand is a long double


Then the other is converted to long double
Else if an operand is double
Then the other is converted to double
Else if an operand is a float
Then the other is converted to float
Else if an operand is an unsigned long
Then the other is converted to unsigned long
Else if an operand is a long
Then the other is converted to long
Else if an operand is unsigned
Then the other is converted to unsigned

In addition, all char and short variables are converted to int. If one operand is long and the other is
unsigned and the value of the unsigned cannot be represented by a long, then both operands are converted
to unsigned long.

These conversions also take place across assignments and in function arguments. In addition, float values are
converted to double. Thus functions take int and double, and not char or float, arguments. In assignments
the reverse operations (involving truncation) can occur depending on the type of the left operand. Not all
compilers follows these conversion rules as accurately as one might hope, so you may need to use explicit type
casting on expressions. For example, if n is an integer variable,

sqrt((double) n);

Even type casting can go wrong on occasion, and you may need temporary variables assigned in separate
statements to get the conversion you require.

Note that the conversion from char to int can cause problems over the sign bit. The top-most bit of the char
value may be interpreted as a sign bit or the compiler may just add a byte of zeros.

4.7 *Operator Precedence*

Table 4.2 shows the precedence of all the C operators. Note that the operators [], ->, ., non-logical &, non-
arithmetic *, and sizeof will be discussed under arrays, structures and memory allocation.
10 CS2150 C Programming for Computer Graphics

Operator Associativity
() [] => . left to right
! ~ ++ -- - (type) * & sizeof right to left
* / % left to right
+ - left to right
>> << left to right
< <= > >= left to right
== != left to right
& left to right
^ left to right
| left to right
&& left to right
|| left to right
?: right to left
= += -= etc. right to left
, left to right

Table 4.2: Precedence of Operators: those on same line have same priority

One very important point to note is that the compiler can rearrange the order of evaluation of expressions. This
means that you cannot assume that expressions will be evaluated as written. For example, in an expression of
the form

f() + g()

f may be evaluated before g or vice versa. This can cause problems if f or g has side effects (e.g. if they modify
global variables). If a particular sequence of evaluation is essential, storing intermediate results in temporary
variables can be used to ensure that it occurs.

Although the associativity of operators is defined, in certain circumstances, even this can be broken. Expressions
involving binary operators that are both commutative and associative (*, +, &, ^, |) can be rearranged even
when bracketed. Thus a+(b+c) may be evaluated as (a+b)+c. Again you may need to use temporary variables
to store intermediate results if side effects make a difference.

C allows this flexibility so that compilers can generate efficient code. However, as the consequence is that certain
aspects of execution cannot be relied upon, you may feel that this feature of the language is actually a piece of
‘broken’ semantics. Note that the left to right execution order for && and || is strictly adhered to.

5 *Statements*

A C program consists of a sequence of statements. There are 10 different statements in C. One of these is the
dreaded goto, about which no more will be said. I have never used a goto in C, and never missed it either.
On the few occasions when drastic long-distance leaps out of a structured control flow are needed (such as
recovering from deeply nested error conditions), the library functions setjmp() and longjmp() should be used.
Note that all statements are terminated by a semi-colon ;.

5.1 *Compound Statements*

Any sequence of statements surrounded by curly braces { and } is treated as a single statement. Common
examples are the bodies of for loops etc.
CS2150 C Programming for Computer Graphics 11

5.2 *Expressions*

An expression can be made into a statement by adding a semi-colon at the end. Of course, if the expression
has no side effects, making it into a statement is fairly pointless:

x>2;
3.72;

For this reason, expression statements are usually one of the assignment expressions or a function call.

x = 3;
i += 4;

5.3 *Conditional Statements*

The usual if else structure is used: the else is optional.

if (expression)
statement 1
else
statement 2

statement 1 is executed if expression has a non-zero value, otherwise statement 2 is executed.

if (a > b) | if (a > b) {
z = b; | foo = bar;
else | z = a;
z = a; | }
| else
| z = b;

In multiple if else statements, each else is associated with the closest previous else-less if.

A great pitfall for novice (and not so novice) programmers is the use of the assignment operator when the
equality test is intended. For example the code

if (i = 1) {
// do something
}

assigns 1 to i and evaluates to 1, so is always true. The code

if (i == 1) {
// do something
}

works correctly.

5.4 *Switch Statements*

Frequently one wants to test whether an expression matches one of a number of constant values and branch
accordingly. While this can be written as a sequence of nested if else statements, it is clearer to use the
12 CS2150 C Programming for Computer Graphics

special switch statement that C provides. The expression must be of char, int or enumerated type. It is best
explained by an example:

VarType var;
....
switch (var) {
case ordinal:
/* Do ordinal type things */
case discrete:
/* ordinal case drops here too */
/* Do things for ordinal and discrete types */
break; /* Don’t fall through to next case */
case continuous:
/* Do continuous type things */
break;
default:
/* Treats all other cases */
/* In this example, it is an error to end up here */
fprintf(stderr, "Unknown value in switch statement\n");
break;
}

The default case is optional, and need not be last, although that is the preferred placement. Cases must all
be different.

5.5 *Loops*

There are three forms of loop statement. In

while (e)
s;

the expression e is evaluated. If it is true (non-zero) the statement s is executed. The expression is then
re-evaluated. This continues until the expression is zero, when execution transfers to the statement after s. Of
course, the statement s is often a block (enclosed in { and } as usual).

The for statement

for (e1; e2; e3)


statement;

is equivalent to

e1;
while (e2) {
statement;
e3;
}

Thus the for loop is a logically redundant language construct. However, for many loops it makes good sense
to have initialisation (e1), test (e2), and re-initialisation (e3) centralised for clarity. The classic example is
iterating over the elements of an array. If a has n elements which are indexed from 0 to n-1, (C’s default array
indexing runs from zero), then the C way to traverse them is:

for (i = 0; i < n; i++)


CS2150 C Programming for Computer Graphics 13

Here is an example which reverses a string ‘in place’ (i.e. without using additional memory). The library
function strlen returns the number of characters in a string. It assumes that the string is terminated with a
’\0’ character.

char s[];
int c, i, j;
for (i = 0, j = strlen(s) - 1; i < j; i++, j--) {
c = s[i];
s[i] = s[j];
s[j] = c;
}

This example also shows the use of the comma operator.

Both for and while loops test the expression at the start of the loop. It is sometimes useful to test the
termination condition at the end of the loop, particularly if the loop must be executed at least once. Routines
that read input from keyboard or file are typically of this sort. C provides a do while loop for this.

do
statement
while (e);

The following do while loop reads integers from the keyboard until it finds one which is negative:

int num;
do {
scanf("%d", &num);
} while (num >= 0);

5.6 *Control Flow Modifiers*

The statements we have introduced so far conform to the rules of structured programming: each statement
(which may be a block of statements) is executed and then control passes to the next statement in sequence.
However, sometimes code can be made more readable and efficient if control does not flow sequentially. Without
mentioning the infamous goto statement (which I have never used in C), there are two useful statements: break
and continue.

The break statement provides an early exit from the innermost enclosing while, for, do loop or switch. The
following loop, which finds the first non-zero element of an array a is a typical example of its use.

for (i = 0; i < n; i++) {


if (a[i] != 0)
break;
}

The value of i is the correct index (or n if no non-zero element is found). This loop could be written as

for (i = 0; i < n && a[i] == 0; i++)

but the termination test is not very natural.

The continue statement applies only to loops. It causes the remainder of the loop body to be skipped and
the next iteration begun. For while and do loops, this means that the test part is executed, but in the for
loop, control passes to the re-initialisation expression. The following code fragment processes only the strictly
positive elements of the array a.
14 CS2150 C Programming for Computer Graphics

for (i = 0; i < n; i++) {


if (a[i] <= 0)
continue;
/* now act on positive elements */
}

6 *Functions*

Functions are a way of decomposing a task into smaller and more easily understood components. A good
functional breakdown is a key part of good design and programming. We have already used some library
functions, such as printf, and in this section we describe the basics of writing your own functions.

6.1 *Syntax*

The preferred syntax for a function is as follows:

return_type function_name(arguments)
{
declarations
statement
}

Arguments should be declared together with their type in a comma separated list. This allows the compiler to
ensure that the the parameters when the function is called have the correct types. A mismatch of arguments
types (for example, by writing the parameters in the wrong order) is one of the most common causes of un-
obvious bugs in C programs.

In the following example, the function foo takes two arguments: bar of type int and baz of type double. It
returns an int.

int foo(int bar, double baz)


{
/* function body */
}

If a function returns no value, it has return type void. Similarly, if there are no arguments, the keyword void
maybe used to indicate this, or the argument list may be left blank.

Functions need to be declared before use. To avoid any problems over dependencies, it is best to place function
declarations at the head of the file. Function declaration differs from function definition in that it just gives the
signature of the function: i.e. the return type and argument types. The declaration should be terminated by a
semi-colon.

6.2 *Arguments, Local Variables and Return Values*

A C function can only return a single value (though later we shall see what can be done if multiple return values
are needed). This is effected by the return statement.

return (expression);

The brackets are optional. A function can contain multiple return statements, but the preferred style is for a
single statement at the end of the function body, unless there is good reason for some other position.
CS2150 C Programming for Computer Graphics 15

All arguments (except arrays) are passed by value, which means that a copy is made of the argument, and the
value in the calling function is unaffected by the computation in the function body. (Again, in a later section
we shall see what to do if you want to modify the value of the variable in the calling function.) Thus, in the
following code fragment:

int x = 6;
int y = 0;
y = foo(x);
/* x still has the value 6 */
....
int foo(int x)
{
x *= 2;
return x + 2;
}

x retains the value 6 in the calling function.

Array arguments are not passed by value, since this would necessitate copying the whole array. There are two
reasons why this is a bad idea: firstly it is obviously very inefficient to copy an entire array; secondly, C arrays
do not necessarily know how long they are, and hence it may be impossible in principle for the compiler to
generate machine code that will copy an array. If a function operates on an array of unknown length, then it
should be declared as a pointer variable (of which, more later) as follows:

return_type function_name(int *a)

In this example, a can be accessed as an array in the usual way. For local variables it is also possible to use the
declaration style

int a[];

as we saw in the string reversing function earlier.

There are mechanisms (such as var_args, which is now in the ANSI C standard) for supplying functions with
a variable number of arguments. However, these are complicated and not always supported by all compilers,
so tend to be non-portable. The most used example of a function which takes a variable number of arguments
is printf, where the format string is used to decide the number and types of the other arguments. If the
arguments you supply do not match the format string, you get some very odd output.

Any variables declared in the function body are local to the function. This means that they have their own
storage allocated when the function is entered, and that storage is relinquished when the function is exited.
These variables supercede any identically named variables in the calling function.

C is not a fully block structured language (for example, functions cannot be defined inside other functions), but
local variables may be declared at the start of any block of statements, and the scoping rules for these variables
are the same.

int bar = 0;
int baz = 0;
baz = foo(bar);
...
int foo(int x)
{
/* This bar is separate from bar in calling function */
int bar = 6;
return x * bar;
}
16 CS2150 C Programming for Computer Graphics

6.3 Recursion

C functions may be used recursively; that is, a function may call itself either directly or indirectly. Typical
examples of this are printing numbers as character strings and traversing recursive data structures, such as
trees. When a function is called recursively, each invocation gets a fresh set of local variables, quite independent
of those used in previous invocations.

Recursion generally provides no saving in storage and is no faster. However, it is usually more compact and is,
in certain cases, easier to write and understand. A very simple example is given by a function to calculate the
factorial of a number. (N.B. this is not the recommended way to calculate factorials except when the argument
is small).

int factorial(int x)
{
if (x > 1) {
return (x*factorial(x-1));
}
else {
if (x >= 0)
return 1;
else {
printf("Error in factorial: negative argument\n");
return 1;
}
}
}

7 *Pointers and Arrays*

Pointers are one of the most dangerous parts of the C language. For advanced programmers, most
bugs are caused by some misuse of pointers.

7.1 *Pointer Variables*

A pointer variable contains the address of a piece of memory. Suppose that px is a pointer variable: it can be
assigned the address of another variable using the operator &:

px = &x;

To access the value that the pointer variable points to (to ‘dereference’ it), the * operator is used:

y = *px;

In this statement, y is assigned the same value as whatever x is pointing to.

A pointer variable is declared in the following way:

type *name;

Pointer variables are useful as function arguments if you want to affect the value of the variable in the calling
function. For example, to write a function swap that swaps its two arguments, pointer arguments are needed:
CS2150 C Programming for Computer Graphics 17

void swap(int *a, int *b)


{
int temp;
temp = *a;
*a = *b;
*b = temp;
}

To swap the values of variables x and y, the correct function call is

swap(&x, &y);

Another common use of pointer variables is to write functions that return more than one value. Be aware,
though, that pointer arguments are not true call by reference parameters. This is because it is possible to use
pointer variables to access other parts of memory due to their relationship with arrays. This is one ‘hole’ in the
language that has been fixed with reference variables in C++.

7.2 *Arrays*

In C there is a strong relationship between pointers and arrays. Any operation that can be achieved by array
subscripting can also be done with pointers. The pointer version will in general be faster but may be harder to
understand.

Pointers and arrays are two ways of viewing one language construct. Array indexing is equivalent to pointer
arithmetic. A simple example will help to clarify this.

int a[10];

defines an array of integers of length 10. This is a contiguous block of memory with space for 10 integers labelled
a[0], a[1], up to a[9].

int *pa;

declares a pointer to an integer. This can be made to point at the first element of a with the assignment

pa = &(a[0]);

Then pa can be used to reference the value of a[0]. So the two lines of C code

x = *pa;
x = a[0];

have the same effect. Indexing other array elements is accomplished by incrementing the pointer so pa+i points
to a[i]. A very important point to note is that this does not depend on the type of a: the pointer arithmetic
is automatically scaled by the size of the objects that the pointer references.

This flexibility of representation is what allows C programs their great control over low level objects. However,
it is also a great source of bugs. It is quite legal to refer to elements outside of the declared size of the array.
In the above example a[-1] and a[10] can be read from and (worse still) written to. Pointers are even more
prone to error if the programmer loses track of the number of elements in the object pointed at or the number
of levels of pointer indirection. It is then very easy to write all over other variables, or even the program itself.

Surprisingly, arithmetic operators can be applied to arrays as well. a+i is a pointer to the ith element of a,
i.e. it is the same as &a[i]. So *(a+i) is equivalent to a[i]. However, pointers are variables, while arrays are
constants and must point to a fixed location. Assignment to an array, such as
18 CS2150 C Programming for Computer Graphics

a = pa;
a++;

are illegal and will be trapped by the compiler.

When an array name is passed to a function, it is the address of the start of the array that is passed. Inside
the called function the argument is treated as a pointer variable that can be accessed either with the syntax for
an array or for a pointer. The declaration is

type *name

or

type name[]

It is also possible to pass part of an array to a function by passing a pointer to the start of the relevant
subsection. So if you want to process a from the third element on, you can use f(&a[2]) or f(a+2) to achieve
this. Another situation where this syntax is useful is if the function assumes that the elements of an array are
indexed from 1 (as is often convenient in numerical analysis or when adapting FORTRAN routines). So if the
function foo has the form

void foo(int *array, int length)


{
int i;
for (i = 1; i <= length; i++) {
array[i] = ....;
}
}

calling foo in the form foo(a, 10) will cause an error, since foo will access elements a[1] to a[10], and the
locations beyond a[9] have not been assigned to a.

To solve this problem foo is passed the address of the location before a starts. Then array[1] will refer to
a[0], and array[10] will refer to a[9]. So foo(a-1, 10) is the correct function call.

Even safer (because it doesn’t rely on the user to call the function in a non-standard way) is to rewrite the
function as:

void foo(int *array, int length)


{
int i;
array--;
for (i = 1; i <= length; i++) {
array[i] = ....;
}
}

Note that the length of the array is passed to foo. This is a very common procedure in C code to avoid functions
going off the end of arrays. In the graphics course we generally use arrays of a fixed size defined using a global
constant, thus we generally don’t worry about passing the array length, which will almost always be three, for
the (x, y, z) coordinates.

7.3 Strings and Character Pointers

String constants are arrays of char locations with an additional element at the end containing the null character
\0 (which always has the value 0). The standard library of C functions also uses the \0 character to mark the
CS2150 C Programming for Computer Graphics 19

end of strings. This means that the length of the string does not have to be passed as an argument. Because
of this, it is sensible to make all your strings null terminated.

As an example, here is a simple implementation of the library function1 strcpy(s, t) that copies t to s. Note
that it allocates no memory: it is assumed that the memory pointed to by s is sufficiently large to contain the
whole of the string t. That is a job for the programmer to get right.

void strcpy(char *s, char *t)


{
while (*s++ = *t++)
;
}

The first point to note is that because the pointers s and t are passed by value, it is safe to modify their values
(i.e. to change the location that they point to) inside the function. The variables in the calling function still
point to the same location. The value of *t++ is the character that t pointed to before it was incremented.
This is copied to the old position that s pointed to before it was incremented. The last character in t is null;
this is copied to s and then the test terminates the loop. You may find it useful to run through the loop with
an example string for t, such as ’a’, ’b’ and ’\0’, and check that it works correctly.

7.4 Memory Allocation and Dynamic Arrays

So far the only way we have seen of constructing arrays is as fixed length data structures (or string constants).
However, often the size of arrays is not known in advance. For example, if you were to write software to
manipulate matrices, it would be of little use if the size of the matrices was fixed.

Local variables (which are called automatic variables) are destroyed at the end of the block containing their
declaration, while global and static variables are always present. Frequently it is necessary to use variables
whose lifetime lies between these two extremes. C provides library functions malloc and free that allow the
programmer to manipulate free store (or heap memory), an area of memory for objects whose lifetimes are
managed directly by the programmer.

Stack: all local automatic variables


**** Empty ****
Heap: explicitly managed by programmer
Static data and globals:
allocated and initialised before main starts
Program

Figure 7.1: Typical Memory Map

Figure 7.1 shows a typical arrangement of memory usage for a C program.

The malloc function returns a heap pointer of type void * which can point to any type of object. If the call is
successful, the return value points to the first byte of memory. If there is not enough available memory to satisfy
the request, malloc returns a null pointer (i.e. one with the value 0). Note that 0 is never a valid value for a
pointer. Memory is allocated in bytes, but using the sizeof operator avoids mistakes and increases portability.
This is an unusual operator in that it works on types and returns the number of bytes needed to store objects
of that type.

Thus the code to allocate a dynamic array of float variables that is n elements in length is given by

float *a;
a = (float *) malloc(sizeof(float) * n);
1 The actual library function returns a pointer to the string copied to.
20 CS2150 C Programming for Computer Graphics

To make this robust, the pointer a should be tested to make sure that it is not NULL before it is used.

if (a == (float *)NULL)
/* error condition */

On all systems used in practice, NULL is the value zero, so the test can be written

if (a == 0)
/* error condition */

If you want to allocate memory for a string, remember that an extra location is needed to store the final \0.

Memory allocated on the heap by malloc cannot be used for any other purpose. However, the object associated
with the memory may no longer be needed after a certain point in the program. To reclaim the memory so that
it can be used for other purposes, the function free should be called for the allocated pointer:

char *p = malloc(42);
...
/* p isn’t needed any more */
free(p);

If p goes out of scope (for example, because execution reaches the end of the block), then you will have a
memory leak: allocated memory that cannot be freed because you can’t access the pointer.

void foo()
{
char *cp = (char*) malloc(5);
}

Here, cp goes out of scope at the end of the function foo without the memory that it references being freed.

The opposite problem is a ‘dangling pointer’: after freeing p, it still points to the same location although it
doesn’t own the memory it points to. Trying to use p again after freeing the memory (assuming that malloc
has not been called to allocate some more heap for p) can give rise to errors that are very hard to track. For
this reason, some software engineers recommend assigning the NULL value to p after freeing it, to ensure that
any attempt to dereference it gives an error.

char *p = malloc(42);
free(p);
p = 0;

x = *p; /* gives a run-time error */

7.5 Matrices and Arrays of Pointers

C does allow the declaration of fixed size two dimensional arrays with the following syntax:

type name[num_rows][num_columns];

Higher dimensional arrays can also be declared in the obvious extension of this notation. Note that these arrays
are stored in row major form, so the rightmost index varies the fastest.

The array
CS2150 C Programming for Computer Graphics 21

int a[3][2];

is stored in the order

a[0][0] a[0][1] a[1][0] a[1][1] a[2][0] a[2][1]

However, just as with fixed size arrays, these are not useful in a lot of practical situations. What we would like
is a two dimensional array whose size can be determined at run time. The way to achieve this is with an array
of pointers. The declaration

int **a;

ways that a is a pointer to an int pointer; in other words, an array of pointers. Let us suppose that we want
to set up a as an m × n matrix. This can be done as follows:

int **a;
int i;
a = (int **) malloc(sizeof(int *)*m);
for (i = 0; i < m; i++) {
a[i] = (int *) malloc(sizeof(int)*n);
}

The elements of a can be referred to in the form a[i][j] just as for two dimensional arrays. Note that we have
a storage overhead for the array of row pointers: m additional elements of storage are used. On the other hand,
speed of access is usually improved, as to find a[i][j] involves adding i to a, a pointer indirection and then
adding j. In a two dimensional array, accessing a[i][j] involves calculating i*m+j, which is usually slower.
Another point to note is that the memory is not allocated as a contiguous block of storage (unlike for a two
dimensional array), as each row can be in a separate part of the heap. Sometimes this can lead to problems of
memory fragmentation. The way around this is to allocate a large block of memory first, and then make the
row pointers point to the correct parts of the large array:

int **a, *temp;


int i, m, n;
/* Initialise m and n */
temp = (int *) malloc(sizeof(int)*m*n);
a = (int **) malloc(sizeof(int *)*m);
for (i = 0; i < m; i++)
a[i] = &(temp[i*n]);

The assignment of row vectors in the loop can also be done as

a[i] = temp + i*n;

Another advantage of this approach is that rows of an array of pointers need not be the same size. This is
particularly useful for arrays of strings, which usually have different lengths, and allocating a fixed length for
all rows would waste memory locations.

7.6 Pointers to Functions

Functions are not variables, so the first question to ask in this section is why one would want to have pointers to
functions? The answer is that is is sometimes useful to be able to pass functions as arguments to other functions,
and function pointers are C’s method of achieving this. One obvious place for this is in a sort function. Every
22 CS2150 C Programming for Computer Graphics

sort algorithm can sort an array of objects of any type provided the objects can be ordered (or compared). Thus
it is logical to pass a comparison function to the sort function as an argument. Suppose that the comparison
function is called comp; it takes two arguments (which for reasons of generality are supposed to be of type
void *; a particular comparison function will cast these to be pointers to the true object type) and returns +1
if the first argument is greater than the second argument, −1 if the second argument is greater than the first
argument and 0 if they are equal. The syntax for declaring comp as an argument to the standard C library
function qsort is

void qsort(..., int (*comp)(void* a1, void* a2))

The brackets around *comp are essential, as otherwise comp is a function returning an int pointer, which is not
what we want at all! As an example, we might write a function to compare two integer numbers:

int intcomp(int *i1, int *i2)


{
return *i1 - *i2;
}

Note that the type conversion from void * to int * is handled by the compiler. Inside qsort, the comparison
function is called as follows:

void qsort(..., int (*comp)(void* a1, void* a2))


{
void* x1;
void* x2;
/* Lots of code */
/* Now compare x1 and x2 */
if (*comp(x1, x2) < 0) {
/* More code */
}
}

We then call the sorting program as follows:

#include <stdio.h>
int intcomp(int *i1, int *i2);

void main(void)
{
int a[] = {1, 4, 2, 5, 3};

qsort(a, 5, sizeof(int), intcomp);


}

int intcomp(int *i1, int *i2)


{
return *i1 - *i2;
}

8 Structures and Object-Based Design

The information storage mechanisms we have discussed so far are ‘flat’. Every object is of the same type and
there is no particular association between them: all elements in an array are treated in the same way. However,
there are many situations where variables can be grouped together in a meaningful structure: a program which
models and supports this data structure will be easier to understand, more portable, and easier to change than
one that doesn’t.
CS2150 C Programming for Computer Graphics 23

8.1 Structure Definition and Element Access

A structure is a single entity or object that contains member variables. Structures cannot contain functions:
that is one of the ways in which C++ classes extends the notion of structures (the other is inheritance). A
structure is declared as follows:

struct name {
type1 variable1;
type2 variable2;
...
typeN variableN;
} [v1, v2, ... , vm];

This defines a structure template, but does not declare any variables unless the optional list of variables is
included. (The square brackets denote the fact that the list is optional and are not part of the declaration.)
For reasons which will be clear when we discuss modularity, it is not usual to declare variables in a structure
definition.

As an example, consider a structure to represent complex numbers:

typedef float real;


struct complex {
real re; /* Represents real(z) */
real im; /* Represents im(z) */
};

The declaration

struct complex z;

defines a variable z which is a structure of type complex. This is a bit of a mouthful, so it is common to define
a typename for a structure. For example:

typedef struct complex Complex;


Complex z;

The only operations that you can perform on a structure are to take its address with the & operator and to
access its members with the . operator. So the assignments

z.re = 0.0;
z.im = 1.0;

make z represent the complex number i. The member names only have to be unique inside the structure
definition.

8.2 Structure Pointers

Structure variables are passed by value as function arguments. This means that a copy of the entire structure
is made. If the structure is large, or you want to modify members’ values, a pointer to the structure must be
passed. To illustrate this, let us write a function to initialise a complex number:

void init(Complex *z, real re, real im)


24 CS2150 C Programming for Computer Graphics

{
z->re = re;
z->im = im;
}

The arrow operator -> is used to reference members of structure pointers. The function body could also have
been written

(*z).re = re;
(*z).im = im;

but this looks more clumsy, and the arrow notation is used almost universally. Given that pointers to structure
exist in C, it will come as no surprise to learn that arrays of structures also exist and work in exactly the
expected way.

Pointers to structures can also be used to define self-referential or recursive data structures. Such data structures,
for example linked lists, trees and hash tables, are popular amongst computer scientists but, with the exception
of trees, tend to be of less use in numerical analysis. Nevertheless, they occur sufficiently often that it is useful
to know how to create them.

Suppose that we are analysing a dataset and want to count the number of occurrences of all the values of a
discrete variable. The list of possible values is (usually) not known in advance, so it cannot be pre-sorted and
the correct count incremented with a binary search (which would be the method of choice). Yet we don’t want
to do a linear search for every value, as this would be terribly inefficient.

The solution we will use here is to keep the set of values seen so far in order. However, shuffling values up and
down an array is not an effective way of doing this; instead we will use a binary tree. We shall suppose that we
have already defined a Value type, and a function compValues that compares two values. The comparison may
not correspond to a ‘real’ ordering of the values but is needed to sort the values in some order. For example, it
could simply compare two values alphabetically.

The tree contains one node per value. The node is defined by

struct node {
Value *val; /* Pointer to a value */
int count; /* number of occurrences */
struct node *less; /* pointer to tree of lower values */
struct node *more; /* pointer to tree of greater values */
};
typedef struct node Node;
typedef struct node *Nodep;

Note that the node contains pointers to instances of itself. (These cannot be declared as struct node as it is
illegal to define a structure that contains a copy of itself. A little thought and some elementary set theory shows
that such a structure would have to be infinite (or empty).) The key function to write is one which will take a
tree and a value as arguments, add the value to the tree if it is not there already, and increment the count if it
is. It should then return the node accessed. Because the data structure is recursive, it is sensible to make the
function recursive too.

Nodep incrementValue(Nodep Np, Value *val)


{
int compare;
if (Np == NULL) {
Np = (Nodep) malloc(sizeof(Node));
Np->val = val;
Np->count = 1;
Np->less = Np->more = (Nodep) NULL;
CS2150 C Programming for Computer Graphics 25

}
else if ((compare = compValues(val, Np->val)) == 0)
Np->count++; /* Have found a match */
else if (compare < 0)
Np->less = incrementValue(Np->less, val);
else
Np->more = incrementValue(Np->more, val);
return (Np);
}

8.3 Modularisation and Scope

Up until now, we have usually assumed that programs are basically all written in one file. However, to write
systems of any complexity, or to reuse software to any significant degree, it is necessary to divide your software
into separate files. This introduces questions of communication between modules, and the scope and visibility of
variables and functions. Note that C is relatively coarse grained in its treatment of these issues in that variables
are either visible only in the file they are defined in or globally. C++ allows a more sophisticated approach to
the problem.

8.3.1 Include Files for Declarations

Suppose that you have decided to put all the functions for arithmetic operations on complex numbers in a file
called complex.c. For another module, say foo.c to us those functions, the function declarations (i.e. the
function template) and structure definitions must be visible inside foo.c. These could be manually copied into
foo.c, but this is a bad idea for two reasons. Firstly, the copying process is liable to introduce errors. Secondly,
the declarations may need to be changed, and this will mean that every module using functions from complex.c
will need to be modified.

The right way to do this is to use include files. C provides a file inclusion feature

#include "filename"

that imports the contents of filename as text in the first pass of compilation. It is conventional to use a .h file
extension for include files. Files are usually included at the head of a source file so that their declarations apply
to the whole file. A file complex.h should be written that contains the structure definition, any typedefs, and
the function declarations (i.e. the function templates without the function body). Note that complex.c can
usefully include complex.h so that the complex structure and all functions are declared before use.

8.3.2 *The static Keyword*

The static keyword is slightly mysterious because it has two rather different interpretations. For global
variables and functions it restricts the visibility of the entity to that module. A global variable is one that is
defined outside any function. Generally speaking, too many globals complicate the interpretation of a program,
as a global variable can (potentially) be modified by any part of the program. The extern declaration is used
to make a global variable visible in another module.

foo.c bar.c

int count; extern int count;


void foo(int i) void bar(int j)
{ {
count += i; count += j;
} }
26 CS2150 C Programming for Computer Graphics

The extern tells the compiler not to reserve any storage for the variable (we want both variables called count
to refer to the same location), but at the linking stage this reference should be resolved to a declaration in
another file (foo.c in this case).

Sometimes a global variable is the easiest way for two functions to communicate. Wherever possible the functions
should be placed in the same file. The global variable can then be declared as static, which restricts its scope
to the file in which it is declared. The canonical example of this is a pseudo-random number generator that
uses a simple linear congruence to generate integers. An additional function is used to set the seed.

static unsigned long next = 1;


unsigned long rand(void)
{
next = next*1103515245 + 12345;
return (unsigned long)(next/65536) % 32768;
}

void srand(unsigned long seed)


{
next = seed;
}

(Note that the parameters used in this implementation are not the optimal ones to use.) Similarly a function
declared as static is visible only from that point on inside the file that it is declared in.

The static modifier can also be applied to automatic variables (i.e. variables declared inside a function or
other block). In these circumstances the variable’s scope is already local to the block and the static does not
change this. Instead it means that the variable retains its value when the function (or block) is exited. Any
initialisation of the variable applies only on the first time that the block is executed. In the following example

void foo(void)
{
static int count = 0;
count++;
}

the function foo accumulates in count the number of times that it is called. Local variables, being in dynamic
memory on the stack do not retain their value between function calls. However, static variables are allocated
permanent space in memory. See figure 7.1.

9 *Summary*

This brief document contains all the information (and more) that you will require to implement the graphics
programming. The extra information will help you if you decide to take your C programming further (for
example on placement). Don’t worry if in simply reading this you have understood little. There are a series of
lab classes where you can get much better help. There is no alternative to getting stuck in and getting your
hands dirty. I provide several template OpenGL programs which you will modify in the lab classes.

You might also like