Sunteți pe pagina 1din 173

1. A Gentle Introduction to C 2. Variables, Operators & Expressions 3. Statements & Flow Control 4. Functions 5. The Preprocessor 6. Arrays 7.

Program Structure 8. Pointers 9. Strings 10. Structures 11. Dynamic Memory 12. Input & Output

2 9 20 36 50 56 62 77 103 113 122 135

Appendix A: Some Example Programs Appendix B: Problem Sheets Appendix C: Past Exam Papers Appendix D: Data Structure Example

151

1. A Gentle Introduction to C
1.1 Basic Hardware concepts

A computer is made up of three main components: 1. The micro-processor (mp) which is only able of performing very basic instructions 2. The memory which stores the data manipulated by the micro-processor. 3. The bus which links the mp to the memory and allow communication.

1.1.1 Memory - the concept of address

The memory of a computer is made of a large number of electronic components which are designed to store the value of a set of eight binary values, a byte. Each of these components is identified by a integer (usually given in hexadecimal notation) called the address. In other words, an address is nothing more than a location in the memory where a byte can be stored. Example: the 6501, a very simple mp, can manage up to 64 Kb of memory. There are therefore 65,536 addresses available to store bytes of information. The addresses start at 0000 and finish at 0xFFFF (which is 65,535 in hexadecimal notation). When the mp requires data, it has to determine its address. This is the only way it can access it. Every object that the computer manipulates has to be represented as one or a sequence of several continuous bytes. The address of an object in memory is the address of the first byte used to store it.

1.1.2The Micro-Processor - The Concept of Machine Instruction

The micro-processor is a electronic component which is able to perform only a fairly limited number of basic operations: 1. store a bit pattern in memory 2. get a bit pattern from memory 3. perform basic arithmetic or boolean operations on a bit pattern 4. jump to "somewhere'' in a program. Actually, the mp jumps to the address where the next instruction to be executed is stored in memory.

These basic operations are the machine instructions. Basic tasks such as the multiplication of two real numbers have to be decomposed in a large sequence of machine instructions. A computer program at the lowest level is nothing more than a sequence of machine instructions operating on the memory.

1.2 The Role of High-Level Languages

The first people to use computers had to write their program directly with the machine instructions and addresses needed. This is both very impractical and error-prone. High level languages are designed to allow easier communication between the user and the machine. While executable code is made of machine instructions and addresses, a high level language is made of statements describing the operations to be done and variables which store the data being processed. Once a program is written in the language, it is transformed into an executable code in two main stages: 1. the compiler translates the high level instructions into machine instructions 2. the linker associates an address with every variable used. A variable is nothing more than a symbolic name for an address in the computer memory.

1.3 A First C program


A C program is collection of functions which operate on variables. A function in C is a sequence of computing operations which are executed in sequence. A function has: 1. a name 2. a number of arguments which provide data for the function (the function may need no argument). 3. a body i.e. a sequence of statements that describe the job to be carried out by the function. 4. a result which is returned (the function may also return no result).

Example of a complete C program:

#include <stdio.h> int main() { printf("Hello world\n"); return 0; }

After compilation and linkage, this program will display the message Hello world on the screen.

Analysis of the program

It consists of a function called main which takes no arguments and returns no value. main is the C equivalent of the main program in Pascal or FORTRAN. A C program starts at the beginning of main. Consequently every C program has to have a main function somewhere. The work done by main is enclosed between the braces {}. Usually, main will call other functions to perform its task. In this case, main only uses one other function, called printf with a single argument: "hello World". It is printf which is responsible for outputting the message. In C a function is called by stating its name followed by a possibly empty list of arguments enclosed in brackets: Function_Name(list_of_arguments);

printf is very commonly used in C programs. It is the basic output routine. After execution of printf, the end of main is reached and control is returned to the calling program. In the case of main, the calling program is always the operating system. Note that printf is not defined in the program. The reason is that printf is a function which is part of the C standard itself. A C program can therefore make use of function that are either written by the program or provided to him by the language standard. C comes in with different collections of such standard functions called libraries. Commonly used library of functions include: 1. input/output operations 2. mathematic functions 3. processing of strings of characters.

The line #include<stdio.h> indicates to the compiler that the input/output library is going to be used in the program.

1.4 Variables in C

Variables are defined by specifying: a) a name b) the type of data they are to contain

C can handle variables containing integer numbers, floating point numbers and a single characters.

For example:
#include <stdio.h > int main() { int sum; sum=10+15; printf("sum of 10+15:%d\n",sum); return 0; }

This program makes use of a variable named sum of type integer (this is a short-cut for the full length definition: sum is a variable which can hold the value of an integer). The line int sum; is a variable definition. As well as giving information to the compiler about the name of the variable and its type, the compiler also allocates the memory corresponding to the variable. In C, a variable definition follows the pattern: type_of_data Variable_Name; The line sum=10+15; is a assignment statement. According to intuition, the value computed from the expression at the right of the sign = is stored in sum. = is called the assignment operator in C The line printf("the sum of 10+15 is %d\n",sum); calls printf for the purpose of outputting information. Here, there are two arguments:

The first argument is the string of characters to be printed. The % indicates where the second argument is to be substituted, and in what form it is to be printed. Here %d means that the second argument is to be printed as an integer (d standing for digit). The second argument, sum, gives the value to be printed. When printf is called, the value stored in sum will be passed the function. This is an example of variable reference. Whenever a variable is encountered in an expression, the value it contains at that time is substituted in the expression.

1.5 Basic Input/Output Operations in C

Basic output is performed by printf. It is a general purpose function to print characters to the screen. Unlike most other functions, it has a variable number of arguments; a call to printf takes in general the following form: printf(format,val1,...,valn);

format is a string of characters which is to be printed. It is composed of i) ordinary characters which are output directly to the screen and ii) conversion specifications. Each conversion specification begins with % and controls the way in which one of the subsequent arguments is converted into printable characters.
val1 to valn are the arguments to be converted. There must be as many

arguments as there are conversion specifications in the format string.

Examples: printf("A string\n") output the message A string and starts a new line. printf("%d\n",sum) prints the value of the variable sum interpreted as an integer. printf("%d %f\n",a,b) prints the values of integer variable a and floating point variable b, separated by a space and followed by a new line. A more detailed description of printf is given later.

1.6 Example of a User Defined Function

The following programme illustrates how a user-defined function can be written and employed in the C language. This program computes a generalisation of the square root:

/* pre-processor directive necessary when using the math library */ #include <math.h> double gen_sqrt(double); /* main function */ int main() { double val,sqroot; /* function prototype */

/* variables */

/* ask the user to enter a real number */ printf("Enter a floating point value > 0"); /* get the value from the user */ scanf("%lf",&val); /* call the function to compute the generalised square root */ sqroot=gen_sqrt(val); /* print out the result */ printf("The generalised square root of %lf is %lf\n",val,sqroot); return 0; }

/* -- user-defined function gen_sqrt --*/ double gen_sqrt(double x) { double result; if(x <0.0) { result=-sqrt(-x); } else { result=sqrt(x); } return (result); }

2. Variables, Operators and Expressions


2.1 Variable Types

A variable is a symbolic name which the compiler associates with an area in memory where a value can be stored. The fundamental characteristic of a variable is the type of data which they hold. This is important because (i) different data types need different amounts of storage space and (ii) different data types are represented differently in binary form. A variable declaration specifies to the compiler: (i) the name of the variable, (ii) its type. In addition, the compiler allocates the necessary memory. Strictly speaking, this is really a variable definition. A variable declaration does not cause the compiler to allocate memory. However, in the common usage, declaration is used in both cases. A C declaration follows the pattern data_type variable name;

C can handle variables of the following data types: characters (which are stored as integer values according to a code - for example the ASCII code): char c; declares a variable c of type character. A variable of type character only hold one character.

2.2 Variable Names


One can also speak of a variable identifier Names are made of letters and digits. The first character must be a letter. The underscore _ is a letter so that names such as the_sum, _value, flag_ are valid. It is best to avoid names starting with an underscore because these patterns are often used for library functions. Upper and lower cases are different and can be mixed. SUM, Sum and sum are all different names. Traditional C usage is that variables are in lower cases and constants in uppercases. Internal names are significant up to 31 characters. External names are guaranteed significant up to 6 characters by the standard and are single case. (This is due to the fact that external names have to be manipulated by linkers, assemblers and loaders over which the language has no control). In practice, external names can often have more than 6 significant characters. There is a list of reserved keywords which cannot be used as variable names:

auto break case char const default do

double int else enum float goto if long

struct switch union unsigned void volatile while

register typedef short signed sizeof static

extern return

continue for

10

2.3 More on Fundamental Data Types

Important properties of a data type are: 1. its size i.e. the number of bits that are used to store a value of this type 2. its range i.e. the interval in which values can be represented by a variable of this type.

C provides a range of fundamental data types (with different sizes and range) in order to suit the needs of the programmers. According to the basic type of data, types belong to one of three categories: 1. Integral Types 2. Character Integral Types 3. Floating Point Types

A character integral type can either be unsigned or signed. If a character variable is stored using 8 bits, as is usually the case, then if we have unsigned char x; can hold values between 0 and 255. if we have signed char x; can hold values between -128 and 127.

The declaration char x; is in practice equivalent to signed char x; A integral type can also be either unsigned or signed, the default being signed. For integral types, There are also three different sizes available (that is the number of bits used to store the number): 1) short int 2) int

11

Examples :
unsigned short int i; signed long int j;

int can be omitted when used with either short, long, signed or unsigned. Thus: long i; is equivalent to signed long int i;

Finally, there are two floating point types: 1. float 2. double for double precision numbers

The C standard does not specify that a double is twice as precise as a float. The only guarantee is that the latter types in the list are at least as precise as those which precede. The actual size of these types is not specified by the standard. The following table shows the size for different platforms:

Dec MIPS Dec Alpha Dec Alpha (OPEN VMS) Type char int float double PC (ULTRIX) (OSF/1) 1 2 4 8 1 2 4 4 4 8 1 2 4 8 8 8 1 2 4 4 4 8 short int 2 long int 4

12

2.4 Constants

There are four categories of constants in C: 1. Integer Constants (such as 63, 0 and 42L) 2. Floating-Point Constants (such as 1.2, 0.00, and 77E2+) 3. Alpha-Numeric Constants (such as 'A' or "Hello!") 4. Enumeration constants

2.4.1 Floating-point constants


The default type of a floating-point constant is double. An F or f is appended to the value, which specifies the float type for the floating point constant.

Examples:

Notation Value .0 0. 2. 2.5 2e1 2E1 2.E+1 2e+1 2e-1 2.5e4 2.5E+4 2.5F

Type

0.000000 double 0.000000 double 2.000000 double 2.500000 double 20.00000 double 20.00000 double 20.00000 double 20.00000 double 0.200000 double 25000.00 double 25000.00 double 2.500000 float

13

2.4.2 Alpha-Numerical Constants

A character constant is any character from the character set enclosed in apostrophes. Note that characters are stored by the machine as (unsigned) integer numbers. The value associated with the character is machine dependent. Very often the ASCII code is used. The following piece of code is valid: char c; c='A';

On a machine using the ASCII code, the value 65 (which is the ASCII code for A), is stored in c. Computers use not only printable characters (letters, digits, punctuation, etc.) but also non-printable characters such as the new-line character, the horizontal tab and the bell. These characters can be entered as escape sequences (which begin with a backslash '\') Non printable characters are:

Character Alert (Bell) Backspace Form feed Newline Horizontal tab Vertical tab

Escape sequence \a \b \f \n \t \v

Carriage Return \r

In addition, some printable characters have to be entered as escape sequences because they have a special meaning in the C syntax.

14

Example: char c='\n'; A string constant or string literal is a sequence of characters enclosed by double quotes ("). Escape sequences can be included in the string. Example of valid string constants are: "Hello world" "Hello world\n" "Ready ? \n Steady \n\t Go!\a\n"

Unlike the FORTRAN read, C does not add a new-line character after a call to printf. The new line has to be given explicitly. Hence: printf("Hello "); printf("World"); will produce the output Hello World But printf("Hello\n"); printf("World"); will produce the output Hello World

and printf("Ready ? \n Steady \n\t Go!\a\n");

15

2.4.3 The const type modifier

Some variables are meant to store constants. This is particularly true in the case of functions parameters. C provides a keyword, const, which informs the compiler that the value stored in a variable is constant and that any attempt by the programmer to modify this value is illegal. For example: const double pi=3.14159;

2.5 Operators and Expressions

An expression is a sequence of computing operations, to which a resulting value can be associated. C has a wider range of expressions than the usual mathematical and boolean expressions. For instance, a variable assignation such as: x=1.0; is an expression.

The value associated with an assignation expression is the value stored in the variable, in this case, 1.0. An operator is "something'' which given some arguments (the operands) will produce a result. C has a very wide set of operators. C has unary operators which take only one argument, binary operators which take two arguments and even a ternary operator which need three arguments!

2.5.1 Arithmetic operators

The usual unary arithmetic operators are: - which gives the negative of the operand + which gives the value of the operand itself (not very useful)

16

2.5.2 Relational and Logical Operators


The relational operators are >, >=, <, <= The equality operator is == The inequality operator is != In C, unlike Pascal or Fortran, there are no boolean types. The integer value 0 is considered to be equivalent to "false''. Any non zero integer value is considered to be equivalent to "true''. A relational operator yields 1 if the specified relation is true and 0 otherwise. The C standard provides two logical operators: inclusive or: || and: &&

Logical expressions are evaluated from left to right and the evaluation stops as soon as the truth or falsehood of the result is known. Like relational expressions, logical expressions have a numerical value: 1 is they are true, and 0 if they are false. There is also a unary negation operator !. The value of the expression is 0 is the argument is non zero, and 1 if it is zero. Example:

int main() { int i=0,j=2; printf("i: %d j: %d\n\n",i,j); printf("i == j: %d\n",i==j); printf("i <= j: %d\n",i <=j); printf("i || j: %d\n",i||j); printf("!i %d !j %d\n",!i,!j); return 0; }

17

2.5.3 Increment and Decrement Operators


The increment operator ++ adds 1 to its operand. The decrement operator -- subtracts 1 from its operand. For example:

int main() { int i=0; i++; printf("i: %d\n",i); return 0; }

will produce the output: i: 1


i++; is therefore equivalent to i=i+1; Similarly, i--; is equivalent to i=i-1; Both ++ and -- can either be prefix or postfix operators. In other words, i++, ++i, i-- and --i are all valid C expressions. However ++i is not equivalent to i++ although they both increment the value of i. With ++i, the value of the expression is the value of i after the incrementation is carried out. With i++, the value of the expression is the value of i before the incrementation is carried.

18

Consider the code: int main() { int i=0; printf("i: %d\n",++i); return 0; }

It will produce the output i: 1 as in the last example because printf outputs the value of the expression ++i, which is the value of i after incrementation. By contrast,

int main() { int i=0; printf("i: %d\n",i++); return 0; }

will produce the output i: 0 because the value of i++ is the value of i before incrementation.

Another example: int main() { int i=0,j,k; j=++i; k=i++; printf("i: %d j: %d k: %d\n",i,j,k); return 0; }

19

3. Statements and Flow Control in C


3.1 Concept of Statement in C

A statement is either an expression followed by a semicolon; a construct which controls the flow of the program the so-called 'null' statement which consists of a single semicolon ;

This statement provides a null operation in situations where the grammar of C requires a statement but the program requires no work to be done. A typical use of the null statement is in loops, as the following example shows: for(i=0;array[i]!=0;i++)

This piece of code finds the first non zero element of array Most expressions are either assignments or function calls: i++; y=sqrt(x); printf("Hello World\ n");

Note that i++, y=sqrt(x), printf("Hello World") are expressions. They only become statements when they are followed by a semicolon. The semicolon in C is a statement terminator rather that a statement separator as in Pascal. Example of statement which control the flow of the programme are selection statements, e.g. the if then else construct, iteration statements, e.g. do while construct, or jump statements such as return or goto.

20

3.2 Compound Statements or Blocks

A compound statement is a sequence of declarations and statements enclosed in braces: { }.

This group of statements can then be treated syntaxically as a single statement. For example, the definition of the function gen_sqrt is a compound statement:

double gen_sqrt(double x) { double result; if(x <0.0) { result=sqrt(-x); } else { result=sqrt(x); } return (result); }

3.3 Selection Statements


3.3.1 The if Statement

The if statement has the following syntax:

if (expression) statement [ else statement ]

21

The statement following the control expression is executed if the value of the control expression is true (nonzero). An if statement can be written with an optional else clause that is executed if the control expression is false (0).

For example:

if (i < 1) funct(i); else { i = x++; funct(i); }

Here, if the value of i is less than 1, then the statement funct(i); is executed and the (compound) statement following the keyword else is not executed. If the value of i is not less than 1, then only the (compound) statement following the keyword else is executed. The syntax of the C language requires that the if and then clause be single statements. This is why a block has to be used when several statements are to be executed. The control expression in a selection statement is usually a logical expression, but it can be any expression of scalar type. Note: the statement if (expression) statement-1 else statement-2 is equivalent to

22

if (expression!=0) statement-1 else statement-2

When if statements are nested, an else clause matches the most recent if statement that does not have an else clause, and is in the same block. For example, if (i { < 1) if (j < 1) funct(j); if (k < 1) /*This if statement is associated*/ funct(k); else /* with this else clause */ funct(j + k); }

Braces can be used to force the proper association: if (b { < 3)

if (a > 3) b += 1; } else { b -= 1; }

A common use of the if then else statement deals with multi-way decisions. The following construction is used: if (expression) statement else if (expression) statement [ else if (expression) statement ] [ else statement ]

23

The expressions are evaluated in order; if any expression is true, the statement associated with the expression is executed and this terminates the whole chain. The last else handles the "none of the above'' case. It can be used for error handling. It can also be omitted.

3.3.2 The switch Statement

The switch statement executes one or more of a series of cases, based on the value of a controlling expression. The switch statement has the following syntax:

switch (expression) { case const-expression: statements [ case const-expression: statements ] [ default: statements ] }

The switch statement is a multi-way decision statement where a single expression is evaluated. It the result matches one of a number of constants, a branching is performed to the statement associated with the constant. The case labelled default is executed if none of the other cases match. It is optional. case and default clauses can occur in any order. The usual arithmetic conversions are performed on the control expression, but the result must have an integral type. The constant expressions must have an integral type. No two case labels can specify the same value. There is no limit on the number of case labels in a switch statement. The following code is a very simple example showing how to use the switch statement for interfacing purposes. Depending on the value of a, functions action1 and action2 can be called or appropriate messages be displayed.
24

The break statement causes the programme to exit from the switch statement Without the break statements, each case would drop through to the next. It is possible to have several cases resulting in the same sequence of statements being executed:

switch(a) { case 1: action1(); break; case 2: case 3: action2(); break; case 4: printf("Exit...\n"); break; default: printf("Incorrect choice\n"); break; }

25

The following example uses switch to count blanks, tabs, and newlines entered from the terminal: #include <stdio.h> int main() { int number_tabs = 0; int number_lines = 0; int number_blanks = 0; int ch; while ((ch = getchar()) != EOF) switch (ch) { case '\t': ++number_tabs; break; case '\n': ++number_lines; break; case ' ' : ++number_blanks; break; default:; } printf("Blanks %d\n",number_blanks); printf("Tabs %d\n",number_tabs); printf("Newlines %d\n",number_lines); return 0; }

Here a series of case statements is used to increment separate counters depending on the character encountered.

26

3.4 Iteration Statements

An iteration statement, or loop, repeatedly executes a statement, known as the loop body, until the controlling expression is false (0). The control expression must have scalar type. There are three different iteration statements: The while statement evaluates the control expression before executing the loop body. The do statement evaluates the control expression after executing the loop body: at least one execution of the loop body is guaranteed. The for statement executes the loop body on the evaluation of the second of three expressions.

3.4.1 The while Statement

The while statement evaluates a control expression before each execution of the loop body. If the control expression is true (nonzero), the loop body is executed. If the control expression is false (0), the while statement terminates. The while statement has the following syntax: while (expression) statement

For example:

n = 0; while (n < 10) { a[n] = n; n++; }

This statement tests the value of n; if n is less than 10, it assigns n to the nth element of the array a and then increments n.

27

The control expression (in parentheses) is then evaluated; if true (nonzero), the loop body is executed again; if false (0), the while statement terminates. If the statement n++; were missing from the loop body, this while statement would not terminate. If the statement n = 0; were replaced by the statement n = 10;, the control expression is initially false (0), and the loop body is never executed. Another example is: while ( n < 1000) { n *= 2; j += 1; }

3.4.2 The do Statement

The do statement evaluates the control expression after each execution of the loop body The do statement has the following syntax: do statement while (expression);

The loop body is executed at least once. The control expression is evaluated after each execution of the loop body. If the control expression is true (nonzero), the statement is executed again. If the control expression is false (0), the do statement terminates.

28

For example: do { n *= 2; j += 1; } While (n < 1000);

Example: a typical use of the do statement is checking input data:

do { printf("y/n?"); scanf("%c",&c); } While((c!='y')&&(c!='n'));

29

3.4.3 The for Statement

The for statement evaluates three expressions and executes the loop body until the second controlling expression evaluates to false (0). The for statement is useful for executing a loop body a specified number of times. The for statement has the following syntax:

for ([expression-1];[expression-2];[expression-3]) statement

The for statement is equivalent to the following while loop:

expression-1; while (expression-2) { statement expression-3; }

The for statement executes the loop body zero or more times. Semicolons (;) are used to separate the control expressions.

30

A for statement executes the following steps: 1. Expression-1 is evaluated once before the first iteration of the loop. This expression usually specifies the initial values for variables used in the loop. 2. Expression-2 is any scalar expression that determines whether to terminate the loop. 3. Expression-2 is evaluated before each loop iteration. If the expression is true (nonzero), the loop body is executed. If the expression is false (0), execution of the for statement terminates. 4. Expression-3 is evaluated after each iteration.

The for statement executes until expression-2 is false (0), or until a jump statement, such as break or goto, terminates execution of the loop. Any of the three expressions in a for loop can be omitted: If expression-2 is omitted, the test condition is always true; that is the while loop equivalent becomes while(1). This is an infinite loop. For example:

for (i = 0; ;i++) statement;

Infinite loops can be terminated with a break, return, or goto statement within the loop body. If either expression-1 or expression-3 is omitted from the for statement, the omitted expression is evaluated as a void expression and is effectively dropped from the expansion.

31

For example: n = 1; for ( ; n < 10; n++) func(n); Here n is initialised before the for statement is executed.

Infinite loops are often used in practice. For example: #include <stdio.h >

void action1(); void action2(); int main() { int a; for(;;) { printf("Enter printf("\t 1. printf("\t 2. printf("\t 3.

a choice\n"); Action 1\n"); Action 2\n"); Exit\n");

scanf("%d",&a); switch(a) { case 1: action1(); break; case 2: action2(); break; case 3: printf("Exit...\n"); return 0; default: printf("Incorect choice\n"); } } return 0; }

32

/* ------ action routines ------ */ void action1() { printf("This is the action1 routine\n"); } void action2() { printf("This is the action2 routine\n"); }

3.5 Jump Statements

Jump statements cause an unconditional jump to another statement elsewhere in the code. They tend to be used to interrupt switch statements and loops. The jump statements are: the goto statement, the continue statement, the break statement, the return statement.

The break statement terminates execution of the immediately enclosing while, do, for or switch statement. Control passes to the statement following the loop or switch body. The syntax for the break statement is break;

33

3.5.1 The return Statement

The return statement terminates execution of a function and returns control to the calling function, with or without a return value. A function may contain a number of return statements. The return statement has the syntax return [expression];

If present, the expression is evaluated and its value returned to the calling function. Very often, the expression is enclosed in brackets: return (0);

A return statement with an expression cannot appear in a function whose return type is void. If there is no expression and the function is not defined as void, the return value is undefined. Reaching the closing brace that terminates a function is equivalent to executing a return statement without an expression.

3.5.2 The continue Statement

The continue statement passes control to the end of the immediately enclosing while, do or for statements. It has the following syntax: continue;

The continue statement can be used only in loops. A continue inside a switch statement that is inside a loop causes continued execution of the enclosing loop after exiting from the body of the switch statement

34

3.5.3 The goto Statement

The goto statement causes unconditional transfer of program control to a labelled statement. The label identifier is in the scope of the function containing the goto statement. The labelled statement is the next statement executed. The goto statement has the syntax goto identifier;

The goto statement is redundant and dangerous. It is always possible to write equivalent code which does not use any goto statement. The equivalent code is often easier to write and usually easier to understand and easier to maintain because it is more structured. The only circumstances where a goto statement may be acceptable is the case of non-trivial error-handling, in a deeply nested code: for (...) { ... for (...) ... if (disaster) goto error; ... ... } ... error: /* error handling */ return;

In this example, the use of a goto statement enable a clean exit from both loops. Here, a break statement would not be sufficient because it would only exit from the inner-most loop.
35

4. Functions in C
4.1 Basic Principles and Definitions

A C program is a collection of user-defined and system-defined functions. Functions provide a useful way to break large computing tasks down into smaller ones which promotes the design of modular programmes that are easier to understand and maintain. A function contains statements to be executed when it is called, can be passed zero or more arguments, and can return a value.

4.2 Function Calls

A function call is an expression, usually consisting of a function identifier followed by parentheses used to invoke a function. The parentheses contain a (possibly empty) comma-separated list of expressions that are arguments to the function. For example, the following is a call to the function power (defined appropriately elsewhere): int main() { ... y = power(x,n); /* function call */ ... return 0; }

In this example, the value returned by the function power is assigned to the variable y. The calling function is also free to ignore the return value of the function. For example, printf is a function which returns an integer, (the number of character displayed). This return value is usually ignored as in int main() { printf("Ignored returned value\n"); return 0; }

36

4.3 Function Types


A function has the derived type function returning type. The type can be any data type except array types or function types, although pointers to arrays and functions can be returned. If the function returns no value, its type is function returning void', sometimes called a void function. A void function in C is equivalent to a procedure in Pascal, or a subroutine in FORTRAN. A non-void function in C is equivalent to a function in these other languages. It is very important for the compiler to know the type returned by any function the program uses. This information can be passed to the compiler through a function prototype. The simplest form of function prototype deals with the case when there is no argument. For example: we want to write a C function my_pi which returns the value of pi using the formula

atan(1.0) = pi/4.

A simple but complete C program defining and using my_pi is:

37

#include <stdio.h> #include <math.h> double my_pi(); int main() { printf("Value of PI: %18.14f\n",my_pi()); return 0; } double my_pi() { return (4.0*atan(1.0)); }

Execution gives the following output: Value of PI: 3.14159265358979

The line: double my_pi(); is the prototype for the function my_pi. It tells the compiler, before the function is used, that it is a function returning a double and taking no argument.

The purpose of the line #include<stdio.h> is to provide the compiler with the prototype for the function printf. The effect of this line is to include the file stdio.h in the source code processed by the compiler. stdio.h is a header file where the prototypes for the standard input/output functions are given. Similarly, the purpose of the line #include<math.h> is to provide the compiler with the prototype for the function atan.

38

Example: declaration of a void function. #include <stdio.h> void message();

int main() { message(); return 0; } void message() { printf("Hello World\n"); }

4.4 Parameters and Arguments


C functions exchange information by means of parameters and arguments. The term parameter refers to any declaration within the parentheses following the function name in a function declaration or definition. The term argument refers to any expression within the parentheses of a function call. A synonym for argument is actual arguments A synonym for parameter is formal arguments Arguments are passed by value; that is, when a function is called, the parameter receives a copy of the corresponding argument's value, not its address as in other languages such as FORTRAN. This is a very important point. Its main consequence is that modifying a parameter does not modify the corresponding argument passed by the function call. Consider the FORTRAN program:

39

program VALUE integer i i=0 call routine(i) write(*,'(A,I3)')'value of i in programme:',i end subroutine routine(i) integer i i=i+1 write(*,'(A,I3)')'value of i in subroutine:',i return end This produces the following output: value of i in subroutine: 1 value of i in program:

Consider now the seemingly equivalent C program: #include <stdio.h> void routine(int); int main() { int i; routine(i); printf("value of i in programme: %3d\n",i); return 0; } void routine(int i) { i=i+1; printf("value of i in subroutine: %3d\n",i); return; } This produces a different output:
40

value of i in routine: 1 value of i in program:

In the FORTRAN program, the modifications to the parameters are mirrored in the arguments. In C, these modifications have no effect on the arguments. C nevertheless provides a mechanism whereby a function can modify a variable in a calling routine. This necessitates the use of pointers

4.5 Function Definitions


A function definition includes the code for the function. Function definitions can appear in any order, and in one source file or several. A function definition has the following syntax: return-type function-name(parameter declarations, if any) { declarations statements }

The type-specifier is the data type of the value returned by the function. If no return value is specified, the function is declared to return a value of type int. A function can return a value of any type except array of type' or function returning type'. Pointers to arrays and functions can be returned.

41

Consider the following definition of the function power int power(int base, int exp) { int n=1; if (exp < 0) { printf ("Error: negative exponent\n"); return -1; } for ( ; exp; exp--) n = base * n; return n; }

This function takes two integer parameters and returns an integer value The sequence power(int base, int exp) is called a function declarator. It specifies the name of the function as well as the name and type of the parameters. A function definition with no parameters is defined with an empty parameter list. An empty parameter list can be specified in two ways: 1. Using the keyword void, if the prototype style is used. For example, char msg(void) { return 'a'; } 2. Using empty parentheses. For example: Char msg() { return 'a'; }

42

4.6 Function Prototypes

A function prototype is a function declaration that specifies the data types of the function parameters in the parameter list, as well as the return type of the function. The compiler uses the information in a function prototype to ensure that: 1. the corresponding function definition and all corresponding function declarations and calls (within the scope of the prototype) contain the correct number of arguments or parameters,and that each argument or parameter is of the correct data type. 2. the return value is consistent for all corresponding declarations as well as the definition.

The function declaration used is the prototype style

Prototype Style: Functions are declared explicitly with a prototype before they are called. Multiple declarations must be compatible; parameter types must agree exactly. Arguments to functions are converted to the declared types of the parameters. The number and type of arguments are checked against the prototype and must agree with or be convertible to the declared types. Empty parameter lists are designated using the void keyword. Ellipses ... are used in the parameter list of a prototype to indicate that a variable number of parameters are expected.

Consider the following definition: char func(int lower, int *upper, char (*func)(), double y ) {}

The corresponding prototype declaration for this function is: char func(int lower, int *upper, char (*func)(), double y);

43

A prototype is identical to the header of its corresponding function definition specified in the prototype style, with the addition of a terminating semicolon (;) or comma (,) at the end, as appropriate (depending on whether the prototype is declared alone or in a multiple declaration).

Function prototypes need not use the same parameter identifiers as in the corresponding function definition because identifiers in a prototype only have scope within the identifier list. For example, the following prototype declarations are equivalent:

char func(int lower, int *upper, char (*func)(), double y); char func(int a, int *b, char (*c)(), double d ); char func(int, int *, char (*)(), double );

The last example shows that identifiers themselves need not be specified in the prototype declaration

4.7 Argument Conversion

If a prototype is in scope, arguments are converted to the type of the corresponding parameters. If no prototype is in scope, integral types are converted to int, and floating point types are converted to double.

44

4.8 Recursive Functions

A recursive function is a function is a function that is defined in terms of itself. It is also called a self-referential function. A recursive function in C is a function whose definition contains a call to the function itself. Perhaps the simplest example of a recursive function is that given by the factorial function: 1 n! = n(n-1)! otherwise if n>0

Here n! is defined in terms of (n-1)!. Note that when the function refers to itself, it uses an argument smaller than the one it was given. In addition the value of the function for the smallest argument is defined without self-reference, so that the function terminates. Recursive functions can be implemented directly in C. For example, the factorial function can be written as: if (n == 0) return 1; else return n * factorial(n-1);

This is often called tail recursion, because the last statement contains a single recursive call. The important point is that sometimes the recursive divide and conquer approach provides a neater and more efficient solution to a problem than the iterative approach. Note that recursive functions have iterative equivalents; for the factorial function this is:

45

int factorial(int n) { if (n == 0) return 1; else { int i, fact = 1; for (i = 2; i <= n; i++) fact *= i; return fact; } }

The iterative implementation of a recursive function is usually a little more efficient. Another example is the power function:

double power(double x, double n) { if (n == 0) return(1); else return(x * power(x, n-1)); }

46

The idea behind recursive algorithms is to divide the original problem up into problems of smaller size. In general, recursive functions may divide the problem up into many subproblems and make several recursive calls in the process of recombining the sub-problems, before generating the solution. This approach is often called divide and conquer and usually leads to much simpler solution of the problem than that taken by an iterative approach. A good example where a recursive approach is also more efficient is provided by the problem of simultaneously finding the maximum and minimum of an array of integers. The iterative solution can be written as follows:

void minmax(int a[], int n, int *p_max, int *p_min) { int i; *p_max = *p_min = a[0]; for (i = 1; i < n; i++) { if (a[i] > *p_max) *p_max = a[i]; if (a[i] < *p_min) *p_min = a[i]; } }

The comparison count for this function is 2(n-1):

since at each of the n-1 iterations of the loop, 2 comparisons are made.

47

The recursive approach can be summarised as follows: if n =1 min = max = a[0]; if n = 2 compare a[0] and a[1] and assign min the smaller and max the larger else divide the array in two, and recursively find the min and max of each half; assign min the smaller of the two min's, and max the larger of the two max's.

The C implementation is:

void minmax(int numberlist, int n, int *p_min, int *p_max) { int min2, max2; if (n == 1) *p_min = *p_max = numberlist[0]; else if (n == 2) { if (numberlist[0] < numberlist[1]) { *p_min = numberlist[0]; *p_min = numberlist[1]; } else { *p_min = numberlist[1]; *p_max = numberlist[0]; } } else { minmax(numberlist, n/2, p_min, p_max); minmax(numberlist + n/2, n - (n/2), &min2, &max2); if (min2 < *p_min) *p_min = min2; if (max2 > *p_max) *p_max = max2; } }

Although not obvious, this algorithm makes slightly less comparisons.

48

The Tower of Hanoi problem is another problem that can be solved recursively. In C, the solution is given by the following program: int main() { int n; printf("Input the number of disks: "); scanf("%d", &n); if (n <= 0) { printf("Number not allowed\n"); exit(-1); } else { hanoi('a', 'c', 'b', n); exit(0); } return 0; }

void hanoi(char from, char to, char other, int n) { if (n == 1) printf("Move disk from %c to %c\n", from, to); else { hanoi(from, other, to, n-1); hanoi(from, to, other, 1); hanoi(other, to, from, n-1); } }

49

5. The C Preprocessor
5.1 Concept of Preprocessor

Preprocessing is the first step in the compilation process. It is optional. According to directives, the pre-processor modifies the source code before it is passed to the compiler proper. The C preprocessor provides the ability to perform macro substitution, conditional compilation, and the inclusion of named files. Preprocessor directives are lines beginning with #.

5.2 Macro Definition (#define)

The #define directive specifies a macro identifier and a replacement list, and terminates with a newline character. The replacement list, a sequence of preprocessing tokens, is substituted for every subsequent occurrence of that macro identifier in the program text, unless the identifier occurs inside a character constant, a comment, or a literal string. #define is often used to define constants or array sizes. For example: #define PI 3.14159 #define TRUE 1 #define FALSE 0 #define NMAX 100 int main() { double a[NMAX]; ... return 0; }

50

The #undef directive is used to cancel a definition for a macro. A macro definition is independent of block structure, and is valid from the #define directive that defines it until either a corresponding #undef directive or the end of the compilation unit is encountered. The #define directive has the syntax: #define identifier replacement-list new-line #define identifier (identifier-list) replacement-list new-line

The first form of the #define directive is called an object-like macro. The second form is called a function-like macro. The #undef directive has the following syntax: #undef identifier newline

The replacement list in the macro definition, as well as arguments in a functionlike macro reference, can contain other macro references. The following example shows nested #define directives: /* Show multiple substitutions and listing format. */ #define AUTHOR james + LAST int main() { int writer,james,michener,joyce; #define LAST michener writer = AUTHOR; #undef LAST #define LAST joyce writer = AUTHOR; return 0; }

After this example is compiled with the appropriate options to show intermediate macro expansions, the following listing results:

51

/* Show multiple substitutions and listing format. */ #define AUTHOR james + LAST int main() { 1 int writer, james, michener, joyce; 2 #define LAST michener 3 writer = AUTHOR; 4 james + LAST 5 michener 6 #undef LAST 7 #define LAST joyce 8 writer = AUTHOR; 9 james + LAST 10 joyce return 0; }

On the first pass, the compiler replaces the identifier AUTHOR with the replacement list james + LAST. On the second pass, the compiler replaces the identifier LAST with its currently defined replacement list value. At line 5, the replacement list value for LAST is the identifier michener, so michener is substituted at line 6. At line 7, the replacement list value for LAST is redefined to be the identifier joyce, so joyce is substituted at line 8. The #define directive can be continued on to subsequent lines if necessary. To do this, end each line to be continued with a backslash \ immediately followed by a newline character. The first character in the next line is logically adjacent to the character that immediately precedes the backslash.

52

5.3 Function-Like Form


The function-like form of macro definition includes a list of parameters. References to such macros look like function calls. When a function is called, control passes from the program to the function at run time; when a macro is referenced, source code is inserted into the program at compile time. The parameters are replaced by the corresponding arguments, and the text is inserted into the program stream. Consequently, for small amount of computing work, macros are more efficient than functions because they do not have the overheads of argument passing. The library macro _trouper, available on some systems in the ctype.h header file, is a good example of macro replacement. The macro is defined as follows:

#define _trouper(c) ((c) (c) & 0X5F : (c))

>= 'a' && (c)

<= 'z' ?

When the macro _trouper is referenced, the compiler replaces the macro and its parameter with the replacement list from the directive, substituting the argument of the macro reference for each occurrence of the parameter (c in this case) in the replacement list. The replacement list of C source code can be translated in the following manner: if parameter c is a lowercase letter (between 'a' and 'z'), the expression evaluates to an uppercase letter (c & 0X5F); otherwise, it evaluates to the character as specified. This replacement uses the if-then-else conditional operator (?:). Macro definition can lead to unexpected results if associativity is not considered.

53

Consider the following macro defining the square of an expression: #define SQUARE(A) A * A

This implementation will give in wrong results for some expressions such as: y=SQUARE(x+1); which after macro expansion is interpreted by the compiler as y=x+1*x+1=2*x+1 instead of: y=(x+1)*(x+1)

The correct implementation of the SQUARE macro is: #define SQUARE(A) ((A)*(A))

Similarly, unwanted side-effects can occur if the increment (++), decrement (-), and assignment operators (such as +=) are used in macro invocation. For example: j=SQUARE(i++); will result in i being incremented twice; j will also be assigned a wrong value.

The solution is obviously: i++; j=SQUARE(i);

54

5.4 File Inclusion (#include)

The #include directive inserts the contents of a specified file into the text stream delivered to the compiler. Usually, standard headers and global definitions are included in the program stream with the #include directive. The #include directive has two forms: #include "filename" newline #include <filename> newline

If the filename is enclosed in quotation marks, the search for the named file begins in the directory where the file containing the #include directive resides. If the file is not found there, or if the filename is enclosed in angle brackets (<>), the file search follows platform-defined search rules. In general, the quoted form of #include is used to include files written by users, while the bracketed form is used to include standard library files. #include <stdio.h> includes the standard header file stdio.h. #include "mydef.h" includes the user defined header file mydef.h

Macro substitution is allowed within the #include preprocessor directive. For example, the following two directives can be used to include a file: #define MACRO1 "file.ext" #include MACRO1

55

6. Arrays
6.1 Basic Definition and Arrays Declarations

Arrays are a derived type because they are defined in terms of another type. Typically, arrays are used to perform operations on some homogeneous set of values. The declaration of an array follows the following pattern: completed-type identifier [[ const-size ]]; Example: int a[10]; defines (i.e. declares and allocates corresponding storing space in memory) an array of 10 consecutive integer, identified by the name a double x[10],y[10]; defines two arrays, x and y, of 10 doubles each.

In C, arrays of any completed type can be declared. These include: 1. fundamental types 2. pointers 3. structures and union 4. arrays

A completed type is a type whose size is known from the compiler. The restriction that array have to be defined in term of a completed type arises because the compiler needs to know the size of an array element in order to access individual elements.

56

6.2 Referencing an Individual Array Element


A particular element of an array is referenced using the [ ] notation. Given a declaration such as int a[10];, a[i] is an expression which value is the ith element of array a In C, arrays start at index 0. Given the declaration int a[SIZE];, elements a[0], a[1], ..., a[SIZE-1] are correct. For example: The following program displays the maximum value of an array of floating point values #include <stdio.h > #define SIZE 10 int main() { double a[SIZE]; double max; int i,n; printf("Enter the number of values >"); scanf("%d",&n); if ((n >SIZE)||(n <=0)) { printf("Illegal size 0 <size <=%d\n",SIZE); return; } for(i=0;i <n;i++) { printf("Enter value %d >",i); scanf("%lf",&a[i]); } max=a[0]; for(i=1;i <n;i++) if(a[i] >max) max=a[i]; printf("Maximum value %lf\n",max); return 0; }

57

C is a very "lazy'' language. It does not check many things (unlike Pascal). There is no mechanism for checking that the index used for referencing an array element is within the bounds of the array. Given the declaration int a[SIZE];, expressions such as a[-1] or a[SIZE+100] are valid. The compiler assumes the programmer knows what he/she is doing!

6.3 Incomplete Array Declarations

Declarations such as int x[]; can be valid in C. Because the size of the array is unknown (at compilation time), this is referred to as an incomplete array declaration. The size of an array declared in this way must be specified elsewhere in the programme. This mechanism can be useful in the context of functions which deal with arrays. For example, if we want to write a function which computes the average of the values of an array, the size of the array is nothing more than a parameter which is important at run-time, not at compile time. The following function definition satisfies this constraint: double average(double a[],int n) { double sum,average; int i; sum=0.0; for(i=0;i <n;i++) sum+=a[i]; average=sum/(double)n; return (average); }

58

The corresponding main program is the following: #include <stdio.h> #define SIZE 10 double average(double [],int); int main() { double a[SIZE]; int i,n; printf("Enter the number of values >"); scanf("%d",&n); if ((n >SIZE)||(n <=0)) { printf("Illegal size 0 <size <=%d\n",SIZE); return; } for(i=0;i <n;i++) { printf("Enter value %d >",i); scanf("%lf",&a[i]); } printf("Average value %lf\n",average(a,n)); return 0; }

Note the way in which the array argument is passed to the function average when it is called: only the identifier a is used.

59

6.4 Multi-Dimensional Arrays


An array in C has only one dimension. Multi-dimensional arrays can be created by declaring arrays of arrays. For example, the declaration: double a[NROW][NCOL]; declares an NROW by NCOL matrix of double.

Internally, because the [ ] operator has left to right associativity, the declaration is interpreted as: double (a[NCOL])[NROW];

In other words, a is an one-dimensional array of NROW objects of type "array of NCOL doubles''. In memory, this result in the array being stored row after row. This is called row major order. For example: consider the declaration int a[2][3]; the elements are stored in memory in the following order: a[0][0], a[0][1], a[0][2], a[1][0], a[1][1] a[1][2]

Note: FORTRAN uses the converse convention: the column major order where multi-dimensional arrays are stored column by column. Given the declaration a[NROW][NCOL]; the expression a[i][j] has the value of the array element at the intersection of the ith row and the jth column. The order in which a multi-dimensional array is stored is important if an efficient code is to be written. It is common to have nested loop to access all the elements of the array successively. The correct looping order in C is for(i=0;i <NROW;i++) for(j=0;j <NCOL;j++) func(a[i][j]); because this minimises the likelihood of page faults on a paginated system.

60

By contrast, but for the same reasons, the correct order in FORTRAN is the reverse: do 10 j=1,ncol do 20 i=1,nrow call func(a(i,j)) 20 continue 10 continue

61

7. Program Structure in C
7.1 External Variables

An internal variable is a variable which is defined within a function. Therefore, it cannot be used outside that particular function because it has no meaning everywhere else in the programme. By contrast, an external variable is a variable which is defined outside any function. Because they are defined outside any function, external variables are available to many functions. For example:

double total; void add(double x) { total+=x; } void subtract(double x) { total-=x; }

Here, both functions operate on the external variable total.

External variables are useful when a set of functions operate on the same group of data. Because data is accessible to all the functions, argument lists are shorter. The typical example is the C implementation of a stack. External variables are nevertheless to be used with caution because they can yield unnecessary data dependencies causing a loss of generality in functions.

62

For example: functions void add2(double x,double *p_total) { *p_total+=x; } void subtract2(double x,double *p_total) { *p_total-=x; } are much more general than add and subtract because they are not restricted to operating on a single variable total.

7.2 Storage Classes for Variables


The storage class of a variable determines the lifetime of the variable. There are two storage classes: 1.automatic variables 2.static variables

7.2.1 Automatic variables

All the variable declarations we have been using so far have been automatic variables. An automatic variable is a variable that comes into existence only when the block within which they are declared is executed. They disappear when the block is exited. Consequently: 1. an automatic variable can only be declared within a function/block, and more precisely, at the beginning of the block. 2. an automatic variable is private to the function in which it is declared. 3. an automatic variable does not retain its value from one call to the other. 4. an automatic variable has to be initialised explicitly at each entry of the function (otherwise it will contain garbage).

63

Automatic variables are often allocated on the stack. An automatic variable is declared by using fundamental-type declarator; For example: int i; char char[10];

Variables with block scope, i.e. variables that are declared at the beginning of a block have automatic storage class by default.

7.2.2 Register Variables

Register variables are automatic variables. However, their declaration hint to the compiler that these variables will be often used. As a result, the compiler may assign them storage in one of the registers of the mp for faster access. However, this is not guaranteed. A register variable is declared in the following way: register fundamental-type declarator;

It is not possible to apply the unary operator & to a register variable, whether or not it is actually stored in a register.

64

7.2.3 Static Variables


Static variables are in existence throughout the duration of the programme. A static object can be declared anywhere a declaration may appear in the programme: 1. inside a block, at the beginning of the block. 2. anywhere outside of a block

Static variables are declared using the static keyword: static fundamental-type declarator;

static variables are initialised only once at the beginning of the programme. The initialisation expression must be a constant. If a static variable is not explicitly initialised, every arithmetic member of that object is initialised to 0 and every pointer member is initialised as a NULL pointer constant. By default, variables declared outside any block have the static storage class. Example : Consider the program: because a is auto while b is static: #include <stdio.h> void func(int); int main() { int n=3; int i; for(i=1;i<=n;i++) func(1); return 0; } void func(int inc) { int a=1; static b=1; a+=inc; b+=inc; printf("a: %d b: %d\n",a,b); }

65

we have the following result: a: 2 b: 2 a: 2 b: 3 a: 2 b: 4

7.3 Scope

The scope of an object is the part of the programme within which the name has a meaning for the compiler. For example, the scope of an automatic variable declared at the beginning of a function is the function itself. The scope of a automatic variable declared at the beginning of a block is the block itself:

void func(int para) { int i; /* scope of i starts here */ ... if(para==0) { int j; /* scope of j stats here */ ... /* scope of j stops here */ } ... /* scope of i stops here */ }

Every declaration has one of four kinds of scope: 1. 2. 3. 4. block scope file scope function scope function prototype scope

66

7.3.1 Block Scope

An identifier appearing within a block or in a parameter list of a function definition has block scope and is visible within the block, unless hidden by an inner block declaration. Block scope begins at the identifier declaration and ends at the closing brace } completing the block. Note: variables used in a block must be declared at the beginning of the block.

Example: int func(void) { int i; double func2(void); ... } Both variable i and the prototype for function func2 have block scope. Example: int main() { int i=0; i++; if(1) /* always true */ { int i=2; i++; printf("i at end of block 2: %d\n",i); } printf("i at end of block 1: %d\n",i); return 0; } This program will produce the output: i at end of block 2: 3 i at end of block 1: 1
67

7.3.2 File Scope

An identifier whose declaration is located outside any block or function parameter list has file scope. An identifier with file scope is visible from the declaration of the identifier to the end of the compilation unit (i.e. the file being compiled), unless hidden by an inner block declaration. In the example below, the identifier off has file scope:

int off = 5; /* Defines the integer identifier off. */ int f(void); /* prototype for function f */ int main () { int on; /* Declares the integer identifier on. */ on = off + 1; /* Uses off, declared outside the function block of main. This point of the program is still within the active scope of off. */ on=f();/* call to f is checked by the compiler because the prototype is in scope */ if (on <=100) { int off = 0; /* This declaration of off creates a new object that hides the former object of the same name. The scope of the new off lasts through to the end of the if block.*/ off = off + on; return off; } return 0; }

68

7.3.3 Function Scope


Only statement labels have function scope. An identifier with function scope is unique throughout the function in which it is declared. Labelled statements are used as targets for goto statements. They should therefore only very seldom used! Labelled statements are declared implicitly by their syntax: label : statement

For example:

int func1(int x,int y,int z) { label: x+=(x+y); if(x >1) goto label; }

7.3.4 Function Prototype Scope

An identifier that appears within a function prototype's list of parameter declarations has function prototype scope: the scope of this identifier begins at the identifier declaration and terminates at the end of the function prototype declaration list.

69

7.4 Linkage

C was designed so that programs could easily be built by linking together groups of functions that had been compiled separately. Technically, a C programme is said to consist of one or several compilation units. A compilation unit is a file containing C source code that can be independently processed by the compiler. If we consider again the example of the generalised square root program and write the gen_sqrt function in a file gen_sqrt.c and the main function in a file tgen_sqrt.c, our program is made of two compilation units which can be compiled separately (for example, by typing the commands

cc -c gen_sqrt.c cc -c tgen_sqrt.c

on a UNIX system) and then linked together to build the executable file (for example, by typing:

cc -o tgen_sqrt tgen_sqrt.o gen_sqrt.o -lm

on a UNIX system - note that -lm includes the math library; this has to be done explicitly on a UNIX system. However, it is done implicitly on most other systems)

The fact that a program can be made of several compilation units means that there is a need for the compiler to distinguish between internal and external objects: this is called the linkage of the object. The linkage is a property of both static variables and functions.

70

7.4.1 Linkage for variables

Linkage properties only make sense for static variables. Since automatic variables are private to a function, linkage issues do not arise. A static variable with external linkage can be referenced by other compilation units (provided it is in scope). A variable with internal linkage can only be referenced within the compilation unit (provided it is in scope). external variables, i.e. variables defined outside any function, have external linkage by default. A static variable can be given internal linkage by using the keyword static: static type identifier; Example: total has external linkage and can be referenced by other compilation units: double total; void add(double x) { total+=x; } void subtract(double x) { total-=x; }

Example: total has internal linkage and cannot be referenced by other compilation units: static double total; void add(double x) { total+=x; } void subtract(double x) { total-=x; }

71

Internal linkage provides added data security. In a self-contained application such as a stack, it ensures that other routines are not allowed to tamper with the data manipulated by the group of functions in the compilation unit.

7.5 Variable Declaration and Variable Definition

A variable cannot be referenced within a compilation unit unless it is in scope, i.e. it has to be declared. The declaration of variables with external linkage raises some issues. Consider for example the following code split into two compilation units:

/* compilation unit 1 */ double var; void func1() { ... var=...; ... } and

/* compilation unit 2 */ double var; void func2() { ... func3(var); ... }

72

These functions are intended to manipulate the same variable var. In fact, this is a wrong implementation. var will be associated with a different address in memory in each compilation unit because it is defined twice. When a variable is defined, it is not only in scope so that it can be referenced but it is also allocated storing space. Consider the correct implementation:

/* compilation unit 1 */ double var; void func1() { ... var=...; ... } and /* compilation unit 2 */ extern double var; void func2() { ... func3(var); ... }

Here, func1 and func2 correctly manipulate the same variable var. This is because we have: 1. a variable definition in the first compilation unit (line double var;) 2. a variable declaration in the second compilation unit (line extern double var;)

73

A variable declaration puts the variable in scope but does not allocate storing space for it. The keyword extern informs the compiler that the variable declared is defined elsewhere. Its syntax is: extern type list-of-declarators; Because storing space is not allocated, incomplete type are allowed in variable declarations. For example:

/* compilation unit 1 */ double a[10]; /* definition */ void func1() { ... var=...; ... } and

/* compilation unit 2 */ extern a[]; /* declaration */ void func2() { ... func3(var); ... }

Every variable with external linkage must be defined only once and declared in all other units.

74

7.5 Linkage of Functions

Like static variables, functions have external linkage by default: they can be called within any compilation unit which makes the program. A function can be given internal linkage, i.e. it can only be called within the compilation unit by using the keyword static: The syntax for the definition of an function with internal scope is: static type function-designator function-body;

It should also be prototyped in the following way: static type functiondesignator; Functions with internal linkage are useful to protect internal functions (i.e. low level functions used by higher level functions within the compilation unit) from outside functions. Note that the distinction between definition and declaration exists also for function. A prototype is a function declaration. This is why the extern keyword can be used with function prototypes to inform the compiler that the function is defined in another compilation unit. For example: consider the two compilation units: /* compilation unit 1 */ double func1(void) { ... } and /* compilation unit 2 */ extern double func1(void); int main() { ... }

extern is, however, optional for function declarations.


75

7.6 Header Files

A header file is a file which contains information that has to be shared by several source files. Header files typically contains: 1. function prototypes 2. constants (macro) definitions 3. type definitions 4. global variables

They are included into the compilation stream by the pre-processor directive #include The C has the following standard header files:

<assert.h> locale.h> <errno.h> <float.h> <ctypes.h> <math.h>

<stddef.h> <stdio.h>

<setjmp.h> <stdlib.h> <signal.h> <string.h>

<limits.h> <stdarg.h> <time.h>

It is good programming practice to create header files because they are a convenient and safe way to insure uniform declarations of global objects for all the files which make up a particular applications.

76

8. Pointers
8.1 Concept of Pointers: Pointers and Addresses

A pointer is a variable which contains the address of another variable (it points' to another variable). Note that a pointer is a variable. In other words, it is a portion of memory where the address of another memory cell can be stored. For example: the 6501 mp can manage up to 64K-bytes of memory. Addresses are integer numbers between 0 and 0xFFFF (in hexadecimal notation). An address needs two bytes to be stored. On a 6501-based machine, a pointer variable will be a group of two continuous bytes in which any address can be stored. In the following example, p is a pointer variable that points' to the variable letter (which contains the character 'A'). On a typical PC, a char is stored in 1 byte and a pointer needs 2 bytes. This example is expressed in C in the following way

char letter='A'; char *p; p = &letter;

Line 1 contains a variable declaration: letter is a variable of type char Line 2 is also a variable declaration: it declares p as a pointer to a char. Finally, in line 3, p is assigned with the address of letter If we suppose that on a particular machine, with a particular compiler, the variable letter is associated with the address 0xFF0, the pointer p will contain the value 0xFFF0

77

8.2 Declaration of Pointer Variables

The pointer type is a derived type. A pointer variable points to variable of a particular type. For reasons that will become clear later, it is important that the compiler knows the size of the object pointed to by a pointer. However, whatever the object it points to, a pointer is always the same size because it always contains the same thing: an address. Pointer declarations follow the pattern: type *variable-name;

8.3 The & Operator

The address of letter is determined by using the & operator. This returns a pointer value that can be assigned to a pointer variable or used as a parameter. A good example is the library function scanf: scanf("%d, &x);

Here scanf is given the address of x as its second argument, so that it can place the value it reads at that address. Using the & argument with scanf is an example of how pointers can be used to have function calls by reference in a C programme instead of function calls by value which is the default.

8.4 The * Operator

To access the memory location to which a pointer variable "points'', the * prefix operator is used on the pointer. For example: *p = 'B';

This C statement stores the code for character 'B' at the address given by the pointer p, i.e. at the memory location pointed to by p. The * operator is also called the dereferencing or indirection operator. One way of defining the * operator is to interpret it as the instruction follow the arrow'. Then *p' can be thought of as follow the arrow stored in p'.

78

If, before the statement *p='B'; there is a statement such as p=&letter; *p and letter then refer to the same contents of the same memory location.

Hence, the statement *p='B'; is rigorously equivalent to letter='B';. *p and letter are not merely equal, but refer to the same physical object in the computer's memory. Hence, changing the value of *p alters the value of letter as the following example shows: #include <stdio.h > int main() { char letter, *p; letter='A'; printf("letter before: %c\n",letter); p=&letter *p='B'; printf("letter after: %c\n",letter); return 0; }

The output of the programme is the following: letter before: A letter after: B

This is one reason why it is necessary to declare pointer variables as pointers to a particular type. When a reference to *p is made, the computer then knows what kind of data to expect.

79

Another example:

int main() { char letter, *p; char character; p = &letter letter = 'A'; printf("letter=%c,*p=%c\n",letter,*p); *p = 'B'; printf("letter=%c,*p=%c\n",letter,*p); p = &character *p = 'Z'; printf("letter=%c,*p=%c,character=%c\n",letter, *p,character); return 0; }

The output of the program is letter=A,*p=A letter=B,*p=B letter=B,*p=Z,character=Z

8.5 Pointers to Pointers and Pointer Casting

Because pointers are "normal'' variables, it is possible to have pointers to pointers. For instance: double **a; declares a as a pointer to a pointer to a double. This construction is very useful in C and will be used later. It is also possible to cast a pointer of a certain type into a pointer to another type. For example: char *charpt; int *intpt; intpt=(int *)charpt;

80

8.6 Pointers and Function Arguments: Difficulties with calls by value

When a function is called, memory space for the declared parameters is allocated, and the values of the arguments are copied into that space. This form of parameter passing is known as call by value. Memory space is then allocated for the local variables used in the function, and execution begins. The fact that the values of the arguments are copied into the space used by the parameters means that a function cannot alter the argument values used in the calling routine. When a function finishes execution, all its parameters are destroyed and their memory space returned to the available memory pool. For example, the following swap program does not work as intended:

#include <stdio.h> void swap(int,int); int main() { int x, y; x = 0; y = 1; printf("in main before swap: x = %d, y=%d\n", x, y); swap(x, y); printf("in main after swap: x = %d, y=%d\n", x, y); return 0; } void swap(int x,int y) { int temp; printf("in swap at start, x = %d and y = %d\n", x, y); temp = x; x = y; y = temp; printf("in swap at end, x = %d and y = %d\n", x, y); }

81

Values are swapped in the function but not in the main program: in in in in main swap swap main before swap: x = 0, y=1 at start, x = 0 and y = 1 at end, x = 1 and y = 0 after swap: x = 0, y=1

This is because parameter and arguments refer to different variables; swap only swaps copies of the arguments.

8.7 Use of Pointers to Implement Call by Reference

It is possible for a function to modify a variable if a pointer to that variable is passed to it. It is thus possible to write a new swap2 of the swapping subroutine that will work properly:

#include <stdio.h > void swap2(int *,int *); int main() { int x, y; x = 0; y = 1; printf("in main before swap: x = %d, y=%d\n", x, y); swap2(&x, &y); printf("in main after swap: x = %d, y=%d\n", x, y); return 0; } void swap2(int *p_x, int *p_y) { int temp; printf("in swap at start,x=%d and y=%d\n",*p_x,*p_y); temp = *p_x; *p_x = *p_y; *p_y = temp; printf("in swap at end, x=%d and y=%d\n",*p_x,*p_y); }

82

Instead of passing x and y, the addresses of x and y are passed. These are copied into the pointer parameters p_x and p_y used by swap2. As a result, p_x and p_y will point to x and y. The situation at the start of swap2 can be represented in the following way: Within the function swap2, the expressions *p_x and *p_y refer the function arguments themselves, not copies. This is why the lines: *p_x = *p_y; *p_y = temp; are modifying the arguments x and y themselves.

The results, this time, are correct: in in in in main swap swap main before swap: x = 0, y=1 at start, x = 0 and y = 1 at end, x = 1 and y = 0 after swap: x = 1, y=0

8.8 Using Pointers for "Returning'' Function Results

Since the return statement can only return one value, pointers can be used as parameters when a function computes two or more values, which it needs to communicate to the calling function. When a pointer is used, the function does not, strictly speaking, return a value: rather it is able to modify the corresponding argument.

83

For example, a program to compute the equation of a line given two points on the line can be written as follows:

#include <stdio.h > int line(double x1,double y1,double x2,double y2, double *p_m, double *p_b);

int main() { double x1,x2,y1,y2; double m,b; printf("Input x,y for point 1: "); scanf("%lf %lf",&x1,&y1); printf("Input x,y for point 2: "); scanf("%lf %lf",&x2,&y2); printf("pt1 (%lf,%lf) pt2(%lf,%lf)\n",x1,y1,x2,y2); if (line(x1,y1,x2,y2,&m,&b)==-1) { printf("Error in line\n"); return 1; } else { printf("y= %lf * x+%lf\n",m,b); return 0; } }

int line(double x1,double y1,double x2,double y2,double *p_m,double *p_b) { if(x1==x2) return -1; else { *p_m=(y2 - y1)/(x2 - x1); *p_b=y1 - *p_m * x1; return 0; } }

84

The function line uses pointers to modify two arguments: the slope (pointer p_m) and the intersect (pointer p_b). This example also illustrates that if pointers are used for "returning'' information from the function to the calling function, then the return value of the function can be used to indicate the success or failure of the function execution. This is good programming practice and should be done as often as possible. Functions can also return pointers of any type.

8.9 Pointers and Arrays: Equivalence between Pointers and Array Identifiers

In C there is a very close relationship between arrays and pointers. A declaration such as: int a[10]; defines a block of 10 consecutive object in memory, each of these objects being able to store an integer value. An array can be pictured in the following way: As seen previously, the notation a[i] gives the ith element of the array. Now, if we consider a pointer to an integer int *pa; and then execute the statement pa=&a[0] we set pa to point to the first element of the array a: Now, by definition, if pa points to an array element, pa+1 points to the next element of the array. In other words, pa+1 is equal to the address of a[1] More generally, pa+i points i elements after pa, and pa-i points i elements before pa. if pa points to a[0], then pa+1 points to a[1], and pa+i points to a[i]. This implies that *(pa+i) refers to the contents of a[i]. In other words *(pa+i) and a[i] are exactly equivalent if pa points to a[0].

85

The following two portions of code are equivalent:

{ double a[10],val; a[1]=val; val=a[9]; } and

{ double a[10],val,*ptr_a; ptr_a=&a[0]; *(ptr_a+1)=val; val=*(ptr_a+9); }

We have just seen that a pointer can be used to access arrays. C goes even further than this: in C, arrays are implemented using pointers. When an array is declared, the compiler: 1. allocates a block of memory to store the array. 2. creates a pointer variable which is initialised with the address at which the block of memory where the array is to be stored start. 3. This pointer is given a symbolic name by the compiler so that it can be accessed by the programmer: the name used is the array identifier.

For example: given the declaration: double a[10];, the compiler allocates a block of 10*sizeof(double) bytes, which start at, say, address 0xA000. It then allocates memory for a variable of type ``pointer to double'' which is initialised with the value 0xA000 and is given the symbolic name a. Hereafter, whenever the array a is in scope, the identifier a can be used as any other pointer to double (except that it cannot be modified).

86

In particular, given the declaration double *pa;, we can have: pa=a; Secondly, when the compiler meets expressions such as a[i], it implements them as *(a+i) In other words, indexing is implemented via pointer indirection: an array declaration results in the implicit declaration of a pointer and access to the array is performed using pointer addition. A pointer implicitly declared when an array is declared behaves in exactly the same way as a normal pointer, except that it cannot be modified. Conversely, any pointer can be used as if it has been implicitly declared by an array declaration. In other words, the [ ] notation can be applied to every pointer whether is it explicitly declared as in: double *ptr; or implicitly declared as in an array declaration. For example: #include <stdio.h > #define MAX 10 int main() { int i,n=MAX; int a[MAX]; int *pa; pa=a; for(i=0;i <n;i++) pa[i]=i; for(i=0;i <n;i++) printf("%3d",a[i]); printf("\n"); return 0; }

Because pa and a refer to the same group of 10 integers in memory, the output of the programme will be: 1 2 3 4 5 6 7 8 9 10

87

8.10 Arrays as Function Arguments

When arrays are passed as arguments to functions, what is passed to the function is not a copy of the array as is the case with other types but the value of the array identifier: For example: supposing we have a function manipulating an array, which prototype is: void ArrayFunction(double a[],size);

The corresponding function call will be:

int main() { double array[10]; ... ArrayFunction(array,10); ... return 0; }

In other words, the argument passed to the function is a pointer to the array. Consequently, the function will be able to modify the element of the array. This type of parameter passing is termed call by reference.

88

Example: the following program uses a function to assign values to an array which are then displayed in the main function:

#include <stdio.h > #define NMAX 10 void input(int a[],int n); int main() { int a[NMAX]; int i; input(a,NMAX); for(i=0;i <NMAX;i++) printf("a[%d]: %d\n",i,a[i]); return 0; } void input(int a[],int n) { int i; for(i=0;i <n;i++) { printf("Enter a[%d]:",i); scanf("%d",&a[i]); } }

In the function definition as well as in its prototype, the parameter a could have been declared as a pointer rather than an incomplete array: void input(double *a,int size) { ... }

Because pointers can be used as array identifiers, the body of the function input is unchanged.
89

8.11 Pointers and Multi-Dimensional Arrays

When a multi-dimensional array is used as a function argument, a pointer is also passed to the function. However, the type of the pointer is more complex. Consider the two dimensional array: double a[2][3]; Similarly to one-dimensional arrays, the compiler creates a pointer associated with the symbolic name a. Because the array is seen by the compiler as an array of 2 arrays of 3 doubles, a is a pointer to an array of 3 doubles. Implicitly, a is declared as: double (*a)[3]; The brackets () are necessary because the declaration double *a[3]; would be interpreted as: (double *)a[3]; which declares an array of 3 pointers to doubles. Consequently, the correct form for the declaration of a two dimensional array parameter in the parameter list of a function is either the array form: type identifier[][ncol]; or the pointer form: type (*identifier)[ncol]; where ncol is the number of column of the array.

90

For example:

#include <stdio.h > #define NROW 3 #define NCOL 2 void func(double (*a)[NCOL],int nrow,int ncol); int main() { int i,j; double a[NROW][NCOL]; func(a,NROW,NCOL); for(i=NROW-1;i >=0;i--) { for(j=0;j <NCOL;j++) printf("%6f ",a[i][j]); printf("\n"); } return 0; } void func(double (*a)[NCOL],int nrow,int ncol) { int i,j; for(i=0;i <nrow;i++) for(j=0;j <ncol;j++) a[i][j]=(double)(i*NCOL+j+1); }

Note that the number of columns has to be explicitly given at the time of compilation, so that the compiler can perform the indexation.

91

8.12 Pointer Arithmetic

Apart from the dereferencing operator, there are other operations which can be performed on pointers. These are: 1. assignment 2. addition and subtraction

8.12.1 Pointer assignment

An address can be stored in a pointer: by using the & operator, as previously:p=&letter by assigning to one pointer the address stored in another:

For example:

int main() { char letter; char *ptr1,*ptr2; letter='A'; ptr1=&letter ptr2=ptr1; printf("letter: %c\n",letter); printf("*ptr1: %c\n",*ptr1); printf("*ptr2: %c\n",*ptr2); return 0; }

The line ptr2=ptr1; results in the address contained in ptr1 being copied into ptr2. Consequently, both ptr1 and ptr2 point to the variable letter.

92

Assignments such as: char *p; p=0xFFF0; where an address is directly assigned to a pointer, are allowed but dangerous: a subsequent statement such as: *p='A'; will store the code corresponding to 'A' at the address 0xFFF0 which may or may not be available for storing data!

This kind of direct assignment can be useful when interfacing with devices which are seen by the mp as a set of addresses. A better way would be to cast the value: p=(char *)0xFFF0; It is also legal to assign to a pointer the value of another pointer pointing to a different type. Code such as

{ char *ptr1; int *ptr2; ptr2=ptr1; }

will often result in the compiler issuing a warning. This type of assignment may be useful in very specific situations. In such cases, it is good programming practice to explicitly cast the value:

{ char *ptr1; int *ptr2; ptr2=(int *)ptr1; }

93

8.12.2 Addition and Subtraction

There are two operations that are commonly performed on pointers, that of integer addition and that of pointer subtraction. As previously noted, there is a close connection between indexing and adding an integer to a pointer. Consider the code: { int i,*ptr; ... ptr=&i ptr=ptr+1; ... } This stores in ptr the first address which is not occupied by the integer variable to which ptr initially points.

The statement ptr=ptr+1 adds to the address contained in ptr one time the number of bytes needed to store an int. This is why, if ptr points to an array element, then ptr+1 points to the next one. Suppose that on a particular architecture, integers are stored on 2 bytes and that ptr initially contains the address 0xA000. After the statement ptr=ptr+1, ptr will contain the address 0xA002. In general, if ptr is a pointer to type type, then the value expression ptr+n is the address stored in ptr plus n*sizeof(type) bytes. For example: Type char int sizeof(type) 1 2 Initial value Value after of ptr ptr=ptr+1; 0x400 0x400 0x400 0x401 0x402 0x402

double 8

94

Another way to look at a pointer addition ptr=ptr+n; where n contains a (signed) integer value, is that after the addition, the pointer will contains the address n units away.

However, the unit' is not the same for all pointers, because it is the size of the data type from which they derive. Subtracting one pointer from another is similar. Consider two pointers to an array sa: p_base = sa; p_tmp = &sa[4];

/* or: p_tmp = sa + 4; */

By subtracting p_base from p_tmp, the appropriate integer value 4 is returned.

8.13 Null Pointers


A null pointer points nowhere in particular. It is like an arrow with no head. A null pointer is C's way of saying that, at least for the moment, this pointer does not address any valid data in memory'. Pointers hold numeric values that represent memory addresses. A null pointer's value equals zero - the only address a pointer cannot reach. Rather than use the literal 0 digit for null, however, you can define a symbol NULL somewhere in a header file: #define NULL 0 Better still is to include a standard header file like stdio.h or stdlib.h. These and others test whether NULL is already defined, and if not, include null.h. Having assigned NULL a pointer value such as: fp = NULL; the program can check whether the pointer is valid: if (fp != NULL) {...}

95

Because NULL equals zero, and because zero means false, it is possible to use the statement: if (fp) {...}

This has an effect identical to that of the previous statement because, if fp is not NULL, it is assumed to address a valid memory location, and its nonzero value is evaluated as true by the if statement. Like all global variables, global pointers are initialised to zero. If pointer fp is declared as a global variable: float *fp; int main() { ... } /* fp == NULL */

fp is guaranteed to equal NULL when the program begins.

If, however, fp or any other pointer is declared as a local variable in a function, like all local variables, the pointer has an unpredictable value when the function runs. To guard against using uninitialised pointers, it is good practice to assign NULL to a local pointer:

void float() { float *fp = NULL; ... }

/* fp == NULL */

96

8.14 Void Pointers

Like a void function that does not return any value, a void pointer points to an unspecified type of data. A void pointer is declared like: void *fp;

The void pointer fp may address any location in memory, and is not bound to any specific data type. A void pointer might address a double variable, a char, or an arbitrary location. A void pointer is a generic pointer for addressing data without having to specify the type of that data in advance. Because the compiler cannot determine from the declaration the size of the object pointed to by a void pointer, it is often necessary to explicitly cast the value of a void pointer before it is used.

8.15 Pointers to Functions

Although functions in C are not variables, it is possible to have pointers to functions. A pointer to a function contains the address in memory of the executable code which makes the function is located in memory. A pointer to a function is declared in the following way: type (*identifier)(parameter-list); To get a pointer to the functions executable code, the following syntax is used: float (* fncptr)(void);

The parentheses around (* fncptr) inform the compiler that the pointer operator (*) binds to the function name, not to its float data type. This creates a pointer to a function that returns a float value, rather than a function that returns a float pointer. Also, any function pointed to by fncptr is expected to have no argument.

97

If the function requires parameters, they are added as usual inside parentheses in the function's parameter list: float (* fncptr)(int x, int y);

This declares fncptr as a function that returns a float value and requires two integer arguments. For example: float (*p_func)(float,float); declares p_func as being a pointer to a function which has two float parameters and return a float value. void (*p_func)(void); declares p_func as being a pointer to a function which has no parameter and returns no value.

Declarations involving pointers to functions can become slightly difficult to read. For instance: double *(*p_func)(void); declares p_func as a pointer to a function returning a pointer to a double. Note that: float (* fncptr)(void); declares fncptr as a pointer to a function returning a float but that: float *ptr; declares ptr as a pointer to a float value, while float *fnc(void); declares a function fnc returning a pointer to a float value.

98

Consider the pointer to a function variable fncptr declared as follow: float (* fncptr)(int x, int y); In order to call a function using a pointer to that function, it is necessary first of all to assign the address of a conforming function to the pointer.

A conforming function is a function which has the same return value type and the same number of arguments of the same type as is declared in the pointer declaration. That is, by declaring a pointer to a function that has two float parameters and returns a float value, the actual function must also have the two expected float parameters and return a float. Suppose, for example, the function to be called is named TheFunction. Assign the function's address to fncptr with the statement fncptr = TheFunction;

Function names such as TheFunction are assumed to be pointers to code and so the address of operator & is not required. TheFunction is written in the normal way: float TheFunction(int x, int y) { ... }

It is possible to call the function directly as in: answer = TheFunction(5, 6); however, because fncptr addresses TheFunction's executable code an alternative way of calling the function is by derefencing the pointer: answer = (* fncptr)(5, 6);

If the function requires no arguments, the parentheses are left blank: answer = (* fncptr)();

99

In each case the parentheses around (* fncptr) instruct the compiler to generate a function call to the code addressed by the function pointer. Pointers to functions are useful in order to code general algorithms which operate on general (mathematical) functions. Example: A good example of the use of function pointers is in a program to plot mathematical functions. An outline program is given below: #include <stdio.h > #include <math.h > #define XSCALE 20 #define YSCALE 10 #define XMIN 1 #define XMAX 79 #define YMIN 1 #define YMAX 25 typedef double (* Pfptr)(int x); void Yplot(int x, double f); double Afunction(int x); int main() { int x; Pfptr pf = Afunction; for (x = XMIN; x <= XMAX; x++) Yplot(x, (* pf)(x * XSCALE); .... return 0; } void Yplot(int x, double f) { /* graphic routine for plotting a point */ } double Afunction(int x) { return sin(x); }

100

Here a typedef is used to create a symbol for the mathematical function to be plotted. The line: typedef double (* Pfptr)(int x); specifies Pfptr as a function pointer type that returns a double value and expects to receive an int argument x.

The mathematical function to be plotted is prototyped as: double Afunction(int x); which conforms to Pfptr's design.

Hence, a pointer of type Pfptr may be used to address Afunction. A Pfptr pointer variable pf is declared and assigned the address of Afunction. To obtain Y coordinate values for the plot, the expression (* pf)(x * XSCALE) calls the function addressed by pf, passing as an argument the value of (x * XSCALE). De-referencing pf as *pf instructs the compiler to call the code that pf addresses (this being the mathematical function to be plotted). Example: writing a general integration routine by quadrature. This is a typical mathematical application where pointers to function are useful. A quadrature algorithm is designed to integrate a general function The algorithm does not depend on the actual function being integrated. This is a concept that pointers to function can express in C.

101

A general integration routine is given below:

#include <stdio.h> #include <stdlib.h> double integrate(double (*)(double),double,double,int); double func(double); int main() { double xmin,xmax; int n; printf("xmin xmax >"); scanf("%lf %lf",&xmin,&xmax); printf("Number of quadrature trapezes >"); scanf("%d",&n); printf("Integral of func is %f\n", integrate(func,xmin,xmax,n)); return 0; } double integrate(double (*f)(double),double xmin,double xmax,int n) { double sum,dx; double x1,x2; int i; if(n <=0) { printf("Illegal value of n\n"); exit(-1); } dx=(xmax-xmin)/(double)n; sum=0.0; for(i=0;i <n;i++) { x1=xmin+i*dx; x2=x1+dx; sum+=dx*0.5*( (*f)(x1)+(*f)(x2) ); } return sum; } double func(double x) { return x; }

102

9. Strings
9.1 Definitions

A string is an array of char values terminated with a null byte equal to zero. Each character in a string is actually an integer byte value associated with either a printable character or an operation such as carriage return or line feed. The string's characters are stored one after the other, with leftmost characters at lower addresses. A null character (represented by \0 in figures) follows the final significant character. For example, string[10]="Hello"; is represented in memory as a character array with the characters H, e, l, l, o, \0:

If the string does not fill the allotted space exactly, bytes after the null terminator are wasted. On a PC, char values in strings represent ASCII characters. The standard ASCII character set specifies symbols for values in the range 0 to 127, with 0 to 30 reserved for control codes. An extended ASCII character set adds more symbols for values from 128 to 255. It is possible to store any of the standard and extended characters in strings. Strings are typically stored in one of three forms: 1. As literal strings entered directly into the programs text. 2. As fixed-size variables stored along with other variables such as integers and floating-point numbers. 3. As pointers that address strings, typically stored on the heap.

103

9.2 String Literals

A literal string is typed directly into the text of a program. Quotes surround the string's characters as in: #define TITLE "My Program"

The constant TITLE is associated with the literal string "My program", which is stored in the program's global data segment. Not seen is the null terminator byte added after the last significant character ('e'), causing the literal string to occupy 13 bytes including the space between the two words. In expressions, literal strings such as "My Program" are treated as the address of the string's first character. Because the compiler sees literal strings as addresses, it is also legal to write: char *stringPtr = "My program";

Variable StringPtr is declared as a char pointer. The assignment initialises StringPtr to the address of 'M', the first character of the string. The characters are stored at a fixed address, which is assigned to StringPtr. To enter long literal strings, type a single backslash at the end of the line to tell the compiler that the string continues on the next line. Because arrays and pointers are equivalent, and since arrays are represented internally as pointers to the address of the array's first element, the following declaration is also acceptable to the compiler: char stringVar[] = "My Program";

This declares stringVar as an array of char values, and is entirely equivalent to the previous char pointer declaration of StringPtr. Internally, StringPtr is a char * that addresses a string. In this case, however, the literal characters from "My Program" are copied to another location reserved for the stringVar array. This form of declaration is used if programme statements modify the string.

104

Use the declaration: char *stringPtr = "My program"; to address a literal string directly, and avoid copying any characters.

To prevent statements from changing the literal string, the const qualifier is added to the declaration: const char *stringPtr = "My program";

It is possible to use either form (char s[] or char *) to declare string pointers. To declare a string as a specific size, array brackets are required, for example: char bigString[80] = "My program";

Then a later statement may store up to 79 characters into the space addressed by bigString. The final character needs to be reserved for the string's null terminator.

9.3 String Variables

A string variable occupies a fixed amount of space determined when the program is written. Strings may be global or local to a function. Since strings are arrays they can be declared using brackets giving the size of the string in bytes char stringVar[128];

The variable stringVar can hold from 0 to 127 characters plus a null terminator. If the declaration is outside any function, it is global, and may be used in any expression. All bytes in global strings (and other global variables) are stored in the program's data statement and set to zero when the program runs. Other modules linked to a program can access the global stringVar by including the declaration:

105

extern char stringVar[128]; which may be included in a header file, thus making stringVar and other global variables available to modules other than the one that declares them.

If stringVar is declared inside a function, then only statements in the function may use it. Local strings (and other local variables) are stored temporarily on the stack and are not zeroed. They contain initial values equal to whatever bytes existed on the stack when the function began, therefore local strings need to be initialised before their use.

9.4 String Pointers

String pointers are pointers i.e. addresses that locate the first character of a string stored elsewhere. String pointers are declared as type char *, or to keep the addressed data unchanged, as const char *. char *stringPtr; /* string pointer */ const char *fixedString; /* pointer to a fixed string */

String pointers are just pointers to arrays of char values, and they behave exactly like other pointers. To allocate heap space for a string pointer, malloc can be used. For example: stringPtr = (char *)malloc(81); reserves 81 bytes of space and assigns to stringPtr the address of the first byte.

The string can hold up to 80 characters plus a null terminator. When finished function free is used to release the memory. free(stringPtr);

106

Since arrays and pointers are equivalent, characters in strings can be referred to using subscript expressions such as: printf("%c", stringPtr[2]);

9.5 Null Strings and Null Characters

Null has many meanings in C programming: 1. A null character has the ASCII value zero and is typically represented in programs by the NULL macro, defined in stdio.h and other header files. NULL may be type int or long depending on the memory model. 2. A null-terminated string is an array of char with an ASCII null character after the last significant character in the string. All strings must have one extra char position to hold a terminating null character. 3. A null string is one that begins with a null character. A null's string length is zero because it has no significant characters. A literal null string is written " ". 4. A null string pointer addresses no character data, it is not equivalent to a null (zero-length) string. To create a null string pointer, assign the macro NULL to the pointer. To create a null string, assign NULL to the string's first byte.

9.6 String Processing


String have to be processed like arrays, i.e. character by character. Special functions, defined in the header file string.h are needed for basic operations such as input/output of strings, string copy and string comparisons. Many more functions are provided, which makes string processing very easy in C.

107

9.6.1 Reading and Writing Strings

The statement: puts(string); displays string, which may be a string variable declared as char string[n], or a string pointer declared as char *string.

Function puts() starts a new display line, making it convenient for displaying multiple strings, perhaps stored in an array: for (i = 0; i < MAX; i++) puts(stringArray[i]);

The easiest way to read a string from the standard input is to call gets(). Since there is no way to tell gets() how many characters to read, the best course is to give it a string variable at least 128 bytes: char buffer[128]; gets(buffer);

To read input into shorter strings, use gets() as above, then call as string function to copy some or all characters from buffer to another variable.

9.6.2 Copying Strings

To copy one string to another, the strcpy() function can be used. For two char * pointers c1 and c2, the statement: strcpy(c1, c2);

copies the characters addressed by c2 to the memory addressed by c1, including the string's null terminator.

A similar function, strncpy, limits how many characters are copied. If source and destination are pointers of type char * or char arrays, the statement strncpy(destination, source, 10); copies up to 10 characters from the string addressed by source to the location addressed by destination.

If the source string has more than 10 characters, the result is truncated. If the source has fewer than 10 characters, the result's unused bytes are set to null.

108

9.6.3 String Comparison

To compare two strings str1 with str2, the strcmp function can be used: if(strcmp(str1,str2)==0) printf("str1 and str2 are equal\n");

Both strings have to be null terminated. Comparisons are made according to the lexicographic order, i.e. the ordering found in a dictionary, with reference to the ordering sequence of the character set. The function return 0 if the strings are equal, a positive number if str1 is greater that str2, and a negative number if str1 is less than str2.

9.6.4 Length of a String

To determine the length of a string string, the strlen function can be used: len=strlen(string);

strlen return an integer, the number of characters in string that precede the terminating \0

109

9.7 Examples of String Processing Functions

Implementation of puts:

#include <stdio.h > int my_puts(const char *); int main() { int status; status=my_puts("This is the string displayed"); printf("return value of my_puts: %d\n",status); return 0; } int my_puts(const char *string) { int i=0; while(string[i]!='\0') { if(putchar(string[i])==EOF) return (EOF); i++; } if(putchar('\n')==EOF) return(EOF); return (1); }

110

Implementation of strlen: #include <stdio.h > int my_strlen(const char *); int main() { char *string="0123456789"; printf("\"%s\" is %d character(s) long\n", string,my_strlen(string)); return 0; } int my_strlen(const char *string) { int i=0; while(string[i]!='\0') i++; return i; }

111

Implementation of strcpy:

#include <stdio.h > char *my_strcpy(char *,const char *); int main() { char *source="This is copied"; char destination[80],*ret_ptr; ret_ptr=my_strcpy(destination,source); printf("source string:\t\t%s\n",source); printf("destination string:\t%s\n",destination); printf("return value of strcy:\t%s\n",ret_ptr); return 0; } char *my_strcpy(char *dest,const char *source) { int i=0; while(source[i]!='\0') { dest[i]=source[i]; i++; } dest[i]='\0'; return (dest); }

112

10. Structures and Other Derived Types


10.1 Structures: Concept of Structures

A structure is a compound type that contains an arbitrary group of related data items. It is similar to the record type in Pascal. It is possible to have any kind of data in a structure, including another structure or an array. A traditional example of a structure is the payroll record: an employee is described by attributes such as a name, an address, a national insurance number, a salary etc. Another typical application is graphics: a point can be seen as a pair of coordinates etc. A structure is defined using the syntax: struct [ tag ] { member declarations };

The keyword struct introduces a structure declaration The structure declaration is the list of declaration enclosed in brackets. tag is called a structure tag and can hereafter be used as a substitute for the part of the declaration between the braces. It is optional. Structures cannot be compared.

113

Example of structure defining a point:

struct point { int x; int y; };

The variables named in a structure are called members. Defining a structure does not declare a variable, rather it creates a new type. To declare a variable of structured type, the syntax is:

struct tag variable-name; if a tag has been defined.

An alternative is:

struct [ tag ] { member declarations }variable-name;

if no tag has been declared.

For example, given the previous definition of structure point, the declaration: struct point pt1,pt2; declares two variables pt1 and pt2: it allocates storage for them in memory.

114

Structures can also be initialised by following its declaration with a list of constant initialisers: struct point pt1= { 0 , 1};

Individual members of a structure can be referred to by the construction structure-name.member

This operation is called a structure reference. Example: printing the element of a structure: printf("pt (%d,%d)\n",pt1.x,pt1.y);

Example: distance of a point from the origin: dist=sqrt((double)pt1.x*pt1.x+(double)pt1.y*pt1.y);

Structures can be nested: for example a triangle can be represented as a set of three points: struct triangle { struct point pt1; struct point pt2; struct point pt3; };

If we declare: struct triangle tr; then tr.pt1.x refers to the x coordinate of the "first'' point defining the triangle

The following are the only legal operations on structures: 1. copying as a unit 2. assigning as a unit 3. applying the & operator 4. accessing its member

115

10.2 Structures and Functions


It is legal for a function to return a structure. A function may also be passed a structure (by value) as a parameter. Example: this function returns the distance between two points:

#include <math.h> double dist(struct point p1,struct point p2) { int dx,dy,d dx=pt2.x-pt1.x; dy=pt2.y-pt1.y; d=sqrt((double)(dx)*dx+(double)(dy)*dy); return d; }

Example: this function returns the mid-point between its two parameters:

struct point mid_point(struct point p1,struct point p2) { struct point mp; mp.x=(p1.x+p2.x)/2; mp.y=(p1.y+p2.y)/2; return mp; }

116

10.3 Arrays of Structures

Two or more arrays of related information can often be translated to a single array of structures, usually with improved efficiency. Consider, for example, the storage of a 100 element set of xyz coordinates. One possibility is: double X[100]; double Y[100]; double Z[100];

To access one data point here requires three array index operations. Using a hypothetical function PlotPoint(), to plot the value at index 9, would require a statement such as: PlotPoint(X[9], y[9], Z[9])

A better solution is to declare a structure with three double members: struct point3D { double X; double Y; double Z; };

Now only a single array is needed to store the 100 value data collection: struct point3D points[100];

Expressions such as points[9] and points[50], refer to the threemember point3D structures stored at those subscripted locations. It is now possible to design a PlotPoint() function to accept a point3D parameter: void PlotPoint(struct point3D p);

117

It is possible to then pass arrayed structures to PlotPoint() using a statement such as PlotPoint(points[9]);

The three indexing operations are reduced to one. Inside the PlotPoint function, the structure members are accessible as p.X, p.Y and p.Z.

10.4 Structures, Pointers and Dynamic Data Structures

A pointer to a structure is the same as a pointer to any of the other basic types.

For example: struct { int i; float f; } s, *p_s; int main() { p_s = &s p_s- >i = 5; p_s- >f = 1.1; return 0; }

The member reference operator -> is used here. The construct p_s->i is just a more convenient way to write: (*p_s).i

Pointers to structures are often used as parameters for efficiency. If a large structure is passed as a parameter, the memory allocation and copying may cause considerable overheads. Pointers however, are usually quite small.

118

Structures can contain pointers among their members. For instance, the following defines a structured type vector which contains the size of the vector and a pointer to the array where the vector is stored: struct vector { int size; double *array; };

In particular, structures can contain pointers to themselves. A simple example is the basic linked list which is built by linking through pointers different variables of the same type: For example: consider the following type definition: Struct list { int info; struct list *next; }

Consider now: int main() { struct list var1,var2,var3; struct list *start; start=&var1 var1.next=var2; var2.next=var3; var3.next=NULL; return 0; }

start can be thought of as an handle to the list. Linked lists are very useful because they can be processed easily and unlike array, they can be built dynamically using dynamic memory allocation.

119

For example: the following loop initialises the member of each variable in the list pointed to by start: struct list *ptr; ptr=start while(ptr!=NULL) { ptr->info=0; ptr=ptr->next; }

Such a linked list is an example of a dynamic data structure. The topic of dynamic data structures is both very rich and very useful in many applications. All dynamic data structures are built using structured types, in the way illustrated above.

10.5 Union

Unions are similar to structures, except that a variable can hold data for only a single member at any given time. For example, the statement: union symbol { char name[4]; int value; }; defines a type that can hold either an array of four char's or an int.

Unions are implemented by allocating enough space for the largest possible member, and overlaying the members. They are most practical when the members use about the same amount of space, or when larger members are stored most of the time.

120

10.6 Type definition

A facility to make new type names in C is provided by the typedef statement. The C compiler recognises the names created with typedef and can perform more complex substitutions. For example, if we consider the structure: struct student_data { char name[80]; int id; };

A typedef statement could then be added of the form: typedef struct student_data student; so that student can be used in declarations instead of struct student_data.

The new name can be the same as the structure tag. The statement typedef struct student_data student_data; is valid in C.

typedef statements can also be combined with structure definitions: typedef struct { char name[80]; int id; }student;

Now student is a new type name, not a variable. Another example is provided by the following: typedef char string[80];

Then declaring the following variables:

string buffer, name; is equivalent to: char buffer[80], name[80];


121

11. Memory Allocation of Matrices and Vectors


11.1 Dynamic Memory Allocation

Very often, it is the responsibility of the compiler to allocate memory for the variables used in a programme: 1. Global variables reside at fixed locations in the program's data segment. 2. Local variables are stuffed on to the stack and exist only while their declaring functions are active.

Both these kinds of variables share one characteristic - they are declared in the program's text. When the compiled code runs, declarations such as int x; or float *fp; define storage space for values of the declared data types. As an alternative, C provides the mechanism of dynamic memory allocation where a programme can be provided storing space at run-time. Such variables are dynamic, they are created on demand and stored in a variable-sized block of memory known as a heap. Dynamic variables are always addressed by pointers. The heap's exact size and location depend on how much memory is available and other factors, such as which memory model the program uses. Think of the heap as a large memory space that tends to provide more room than is typically necessary for global and local variables, and is therefore a good place to store large buffers and data structures that grow and shrink as needed to accommodate varying amounts of information. The most common technique for reserving space on the heap for dynamic variables is through the use of the library function malloc (memory allocator). As expected, dynamic memory allocation is used in C when it is not known in advance how much space will be required.

122

A good example where dynamic memory allocation is useful is image processing. In image processing applications, the amount of space needed is strongly dependent upon the size of the image processed. Another typical example is linear algebra packages. Dynamic memory allocation is achieved by a call to a function that returns the address of some new' space. This address can then be stored as a pointer variable and accessed using standard pointer operations. The library function used is called malloc. This function returns a void pointer, which then needs to be cast to the appropriate pointer type. It has one argument, the amount of space to allocate (in bytes). Each data type (e.g. int, float, structures) may require a different amount of space, and this itself is machine-dependent. C provides an operator (sizeof) that determines the amount of space required to store its operand (in bytes). The sizeof operator has a single operand enclosed in parentheses. The operand may be either a type or an expression.

123

For example, the program below uses malloc to allocate space for an array of int. The number of elements in the array is input by the user, and that value is multiplied by the value of sizeof(int) to determine the actual number of bytes to allocate. The result is then cast to a pointer to an int. int main() { int *p_Array; int i, count; printf("How many integers? "); scanf("%d", &count); p_Array = (int *) malloc(count * sizeof(int)); if (p_Array == NULL) { printf("Could not allocate the space."); exit(1); } printf("Enter %d integers.\n", count); for (i = 0; i < count; i++) { scanf("%d", &p_Array[i]); } ... ... free(p_Array); return 0; }

Since space is allocated in consecutive bytes, the pointer returned by malloc can be treated as though it were the base of an array and array subscripting can be used.

124

The loop that reads the integer values could equally be written as for (i=0;i<count;i++) scanf("%d",p_Array+i);

When the space allocated is no longer needed, it can be returned to the operating system for use during the next dynamic memory allocation. This is achieved through the use of the library function free, which has one argument: the pointer to the space to be freed. Once freed, the space should not be accessed even though a pointer variable may seem to point to a valid location. In fact it is good programming practice to assign such pointers the value NULL (defined in the include file <stdio.h>). This guarantees that the pointer does not correspond to any real pointer value.

11.2 Dynamic Memory Allocation of One-Dimensional Arrays (Vectors): Principle

The close correspondence between pointers and arrays allows the block of memory returned by malloc to be considered as an array. As we have seen previously, the value referenced by an expression like a[j] is also referenced by *(a+j), that is, the contents of the address obtained by incrementing the pointer a by j'. An array a of any type Type and arbitrary size n (small enough to fit in memory!) can be allocated in C by the following call to malloc: a=(Type *)malloc(n*sizeof(Type));

If the allocation is successful, a is non null and can hereafter be used as a normal array.

11.3 Modifying the Offset of a One-Dimensional Arrray

Arrays in C begin at 0 and an array declared by the statement float b[4] has the valid references b[0], b[1], b[2], b[3], but not b[4]. In general the range of an array declared by float a[M] is a[0...M-1], this being the same if float is replaced by any other data type.

125

There are many algorithms that more naturally go from 1 to M rather than 0 to M-1. For example, the coefficients of a polynomial A0+a1x+..+anxn are naturally a zero-offset vector.

A vector of N data points xi, i = 1,..,N, however, is naturally a unitoffset x[1...N]. Although it is possible, if a unit-offset array x[1..N] is needed to declare it as double x[N+1]; and only use indices 1 to N, it is better to use the power of C to solve this problem. This can be done as follows: float b[4], *bb; bb=b-1;

The pointer bb now points one location before b. Hence, the array elements bb[1], bb[2], bb[3], and bb[4] all exist because they have the same addresses as b[0], b[1], b[2] and b[3] respectively. The range of bb is bb[1..4]. Now bb is a unit-offset vector. This "trick'' can be illustrated as follow: A routine with an array as an argument can appear as: void someroutine(float bb[],int nn) { /* this routine does something with the vector bb[1..nn] */ }

Then calling this routine with a vector of unit-offset, say aa with aa[1..7], can be done as: someroutine(aa,7). If however, the vector of length 7 has zero-offset, say a[0..6], then the routine can be called as someroutine(a-1,7). Now a[1], as seen from within the routine, is actually a[0] as seen from the calling function.

126

11.4 Application

Dynamic memory allocation and array offsetting can be combined to declare arrays with arbitrary offset. The following function, slightly modified from the ''Numerical Recipe'' library, allocates a vector of float starting at index nl and finishing at index nh:

#include <stdlib.h> float *vector(int nl,int nh) { float *v; v=(float *)malloc((unsigned)(nh-nl+1)*sizeof(float)); if(!v) return NULL; v=v-nl; return v; }

malloc allocates nh-nl+1 floats which is the desired size. If the allocation is unsuccessful, vector finishes returning NULL to the calling function. At this stage, expressions v[0] to v[nh-nl] are valid. The integer nl is subtracted from v and the value stored in v. so that v points nl floats before the start of the allocated memory. Because of this, only expressions v[nl] to v[nh] are now valid.

127

An associated main program is: float *vector(int,int); /* prototype is necessary */ int main() { int i; int nl=2; int nh=12; float *v; if((v=vector(nl,nh))==NULL) { printf("Memory Allocation failure"); exit(1); } for(i=nl;i<=nh;i++) { printf("Enter v[%d]:",i); scanf("%f",&v[i]); } ... free_vector(v,nl); /* deallocation */ return 0; }

Because v is offset, free(v) would not deallocate the memory because it does not recognise v as a pointer to a block previously allocated. This is why it is necessary to write the function free_vector to perform this task: void free_vector(float *v,int nl) { free((void *)(v+nl); }

Here, it is the starting address of the block allocated in vector which is passed to free.

128

11.5 Matrices and Two-Dimensional ArraysAn Alternative Data Structure for Matrices

It is common in scientific programming to deal with two-dimensional arrays whose size is variable and known only at run time. The C language has a method for dealing with such variable dimensions. Consider the array reference to a float value a[i][j], where i and j are expressions of type int. If a has been declared as a fixed-size array, e.g float a[5][9], then the effect is to the address a add 9 times i, then add j, return the value thus addressed'. Note that the constant 9 needs to be known in order to effect this calculation, and an integer multiplication is required. Multi-dimensional arrays can be implemented more efficiently using pointers: A matrix a declared as type a[nrow,ncol]; can be represented as a set of nrow rows, each being a one-dimensional array of ncol elements of type type. Now consider an array aa of nrow pointers to type. This can be declared as: type *aa[nrow];

We can initialise each pointer element of array aa so that it points to the corresponding row of the matrix: for(i=0;i <nrow;i++) aa[i]=&a[i][0];

Hence, aa[i] points to the i-th row of the matrix. Then because aa[i] is a pointer, the expression (aa[i])[j] is valid and is interpreted by the compiler as: 1. take the address stored in aa[i] 2. add j to it 3. return the value thus addressed

129

Because aa[i] points to the ith row of the matrix, (a[i])[j] gives the element aij of the matrix! In fact, because the [] associate from left to right, (aa[i])[j] can be written without brackets: aa[i][j]. aa can hereafter be used as a normal two-dimensional array. The expression aa[i][j] is interpreted as to the address of aa add i, take the value thus addressed by this new address, add j to it, return the value thus addressed'. Note that the underlying size of the matrix does not enter the calculation at all, and there is no multiplication; here an additional indirection replaces it. This second scheme is, in general, faster and more versatile than the first. The price paid is the storage requirement for one array of pointers (to the rows of a[][]), and the slight inconvenience of remembering to initialise these pointers when we declare an array. Example: shown below is an example of the conversion of how a fixed-size array a of size 13 by 9 to a pointer to array of pointers' reference aa:

float a[13][9], **a; int i; aa=(float **)malloc((unsigned) 13*sizeof(float*)); for (i=0;i <=12;i++) aa[i]=a[i]; /* a[i] is pointer to a[i][0] */

It is possible to manipulate any element of the matrix a by referring to it as aa[i][j] More importantly, it is possible to pass the matrix to a function using the identifier aa.

130

Suppose we have a function sum which sums the entries a MN matrix. We can write: square(aa,m,n,&sum); instead of square(a,m,n,&sum); Although aa and a refer to the same matrix, these two statements are not equivalent. In each of these calls a pointer is passed to square but the types of the pointer are different. aa is a pointer to a pointer to a float: in C syntax: float **aa; which is interpreted by the compiler as (float *)*aa;

a by contrast is a pointer to an array of 9 floats. In C syntax: float (*a)[9];

Consequently, If a is passed, square has to be written as: void square(float (*mat)[9],int m,int n,float *sum) { int i,j; *sum=0.0; for(i=0;i <n;i++) for(j=0;j <n;j++) *sum+=mat[i][j]; return; }

131

If aa is passed, square can be written as: void square(float **mat,int m,int n,float *sum) { int i,j; *sum=0.0; for(i=0;i <n;i++) for(j=0;j <n;j++) *sum+=mat[i][j]; }

Note that it is only the parameter lists that differ. The body of the function is the same in each case. The second version is vastly superior to the first because the physical size of the matrix does not interfere at all with the code of the function. The size of the matrix is only relevant at run-time. By contrast, the first version is limited to arrays which are declared at compile time as having a certain number of columns. This is a severe limitation, mirrored in FORTRAN 77. Hence it is best to avoid using fixed-size two-dimensional arrays in C as data structures for matrices. Instead the pointer to array of pointers' approach is preferred, with the array elements pointing to the first element in the rows of each matrix.

132

11.6 Dynamic Allocation of Matrices

Until now, aa has been defined as a more convenient alias for the matrix a whose size has been fixed at compile time. Dynamic memory allocation, however, allow more flexibility still: storage space can be allocated for aa, at run-time without reference to a fixed size array in the following way, so that memory is efficiently managed. Suppose we want to allocate a nrow by ncol matrix of type type: 1. declare a pointer ptr to a pointer to type 2. use malloc to allocates an array of nrow pointers to type and store the result in ptr 3. use malloc to allocate nrow one-dimensional arrays of ncol elements of type type and store the results in the previously declared array of pointers.

As in one dimension, matrices with a given offset can be created. The following function, adapted again from "numerical recipe'', allocates a matrix of double with range [nrl..nrh][ncl..nch] and returns to the calling function a double ** pointer:

/* Allocates a float matrix with range [nrl..nrh][ncl..nch] */ double **matrix(int nrl, int nrh, int ncl, int nch) { int i; double **m; /* Allocates pointers to rows */ m=(double **)malloc((unsigned) (nrh-nrl+1)*sizeof(double*)); if (!m) return NULL; m -= nrl; /* Allocate rows and set pointers to them */ for(i=nrl;i <=nrh;i++) { m[i]=(double *)malloc((unsigned) (nch-ncl+1)* sizeof(double)); if (!m[i]) return NULL; m[i] -= ncl; } /* return pointer to array of pointers to rows. */ return m; }

133

An example of matrix is:

#include <stdlib > double **matrix(int,int,int,int); void sum(double **,int,int,double *); void free_matrix(double **,int,int,int); int main() { double **a,sum; int ncol,nrow; ... if((a=matrix(0,ncol-1,0,nrow-1))==NULL) { printf("Allocation error"); return 1; } ... sum(a,nrow,ncol,&sum); ... free_matrix(a,0,nrow-1,0); return 0; }

free_matrix is used to deallocate a matrix allocated by matrix:

void free_matrix(double **m, int nrl, int nrh, int ncl) /* frees a matrix allocated with matrix. */ { int i; for(i=nrh;i >=nrl;i--) free((void*) (m[i]+ncl)); free((void*) (m+nrl)); }

134

12. Input and Output in C


12.1 Concept of Stream

C does not provide input/output facilities in the language standard. C programme relies on a set of functions to perform even basic output and input tasks. These functions are part of the C standard and form the standard input/output library whose header file is <stdio.h> A C programme inputs data from and output data to a stream. A stream is simply a sequence of bytes that can be read or written one by one. Examples of streams are files, the keyboard, the monitor.

12.2 Standard Input/Output


A text stream is a stream which can be interpreted as a sequence of characters. In contrast, a binary stream is a stream which can be interpreted as a sequence of binary values. A text stream is seen as a sequence of lines, each terminated by a new-line character. A C programme will make three text streams available automatically: 1. standard input, defined as stdin in <stdio.h>, usually connected to the keyboard 2. standard output, defined as stdout in <stdio.h> usually connected to the screen 3. standard error, defined as stderr in <stdio.h>, usually connected to the screen as well.

135

12.3 Some Basic Functions for Standard Input and Output

There are two groups of operations that can be performed on stdin and stdout: one is formatted operations where internal binary values are translated into characters or vice-versa. printf and scanf belong to that group. The other group contains functions that directly manipulates characters: getchar reads one character from stdin gets reads a line from stdin putchar writes one character to stdout puts writes a line to stdout

12.3.1 Detailed description of printf


The printf function performs formatted output. Prototype of printf: int printf(const char *format,...);

printf returns an integer value: the number of characters written on the terminal. The ellipsis ... means that after the format string, there can be any number of arguments of any type. The format string contains two types of characters: i) ordinary characters which are copied into the output stream stdout directly and ii) conversion specifications which control the conversion of the next successive argument into printable characters.

Each conversion specification starts with a % and ends with a conversion character (c.f. following table).

136

Between % and the conversion character, there may be in order:

Flags (in any order) which modify the specification: [-] specifies left adjustment of the converted argument in the field [+] specifies that numbers will always be printed with a sign [Space]: if the first character is not a sign, a space will be prefixed [0]: for numeric conversions, specifies padding to the field with leading zeros

A number specifying the minimum field width. This is a minimum value: if the converted argument is wider, it will be printed completely without truncation. A period which acts as a separator when the precision is present. A number, the precision, that specifies the maximum number of characters to be printed from a string, or the number of digits to be printed after the decimal point, or the minimum number of digits to be printed for an integer. A length modifier: h indicates that the corresponding argument is to be printed as a short or unsigned short; l indicates that the corresponding argument is to be printed as a long or unsigned long; L indicates that the argument is a long double.

137

Character d,i o x,X u c s f

Argument type int int int int int char * double

Converted to Signed integer (decimal notation) Unsigned integer (octal notation) Unsigned integer (hexadecimal notation) Unsigned integer (decimal notation) Single character after conversion to unsigned char Character from a string are printed until a \0 is reached Floating point number (decimal notation) of the form [-]mmm.ddd where the number of d's is specified by the precision.

e,E

double

Floating point number (decimal notation) of the form [-]m.dddexx where the number of d's is specified by the precision.

g,G

double

Floating point number (decimal notation); equivalent to e or E if the exponent less than -4 or greater than or equal to the precision; otherwise, equivalent to f

p %

void *

Prints a pointer in a format which is implementation dependent. No argument converted; prints a %

138

12.3.2 Detailed description of scanf


The scanf function performs formatted input conversion. Its prototype is: int scanf(const char *format,...)

scanf reads input from the keyboard (stream stdin) and converts it into values under the control of the format string. The values are then stored at the addresses given by the subsequent arguments (which must therefore be pointers). The format string may contain: 1. blanks or tabs which are ignored 2. ordinary characters which are expected to match the next non-white character in the input stream 3. conversion specifications, consisting of a %, an optional number specifying the maximum field width, an optional character h,l or L indicating the width of the target variable and a conversion character.

Some conversion characters are given by the following table:

Character Argument type d i o u x c s e,f,g int * int * int * unsigned int * int * char * char * float *

Input data Decimal integer Integer; it may be in octal if there is a leading 0 or in hexadecimal if there is a leading 0x or 0X Integer in octal notation Unsigned decimal integer Hexadecimal integer Characters; the next input characters are placed in the indicating array up to the number given by the width field; the default is 1; No \0 is added String of non-white space characters; a \0 is added Floating point number

139

The parameter indicating the width of the target variable is important. Consider the statement scanf("%f",&a); where a is a double. This will not work properly because the input will have been converted to float by scanf. Correct conversion specifications for double are %lf or %le or %lg Similarly, for a short and for a long, possible conversion specifications are %ld and %lf respectively.

12.3.3 Problems with scanf

scanf ignores white-space characters in the input. In C, the new-line character is considered (with others) to be a white-character. The consequence is that if scanf expects, say, three input fields on a line, and two are actually present before a new-line character is read, scanf carries on to the next line of input to read the third value. It is also difficult to mix calls to scanf with other I/O routine. The following is a common piece of code: int main() { int i; printf("Enter the data"); scanf("%d",&i); /* do the work and output the result */ printf("press RETURN to continue\n"); getchar(); return 0; }

The call to getchar is intended to pause the program until the user is ready to proceed. This does not work properly because of the behaviour of scanf. scanf reads characters from the keyboard up to the new-line character. When the new-line character is read, scanf realises that the only input field it expects is finished. Therefore, it converts it into an integer value and puts back into the input stream the new-line character by a call to ungetc. Therefore, when getchar is called, it finds a character in the input stream, the new-line character, and returns immediately so that the program is not paused.

140

12.3.4 Better Alternatives to scanf

Input should be only performed by a call to gets, which reads the standard input until a new-line character is found and then puts the result in a string. Conversion can then be performed in two ways: 1. If there is only one value to convert, atof and atoi can be used.

Example: input of an integer value using atoi: #include <stdlib> int main() { char buf[80]; int i; gets(buf); i=atoi(buf); return 0; } Example: input of a double value using atof: #include <stdlib > int main() { char buf[80]; double a; gets(buf); a=atof(buf); return 0; }

141

2. If the conversions are more complex, sscanf can be used. This function is very similar to scanf but takes the input from a string.

For example:

#include <stdlib > int main() { char buf[80]; int i; gets(buf); sscanf(buf,"%d",&i); return 0; }

The key element of this solution is that the gets reads the new-line character but does not put it back into the input stream nor does it copy it into the result string. Consider: #include <stdio.h > int main() { int i; char buf[80]; printf("Enter the data >"); gets(buf); sscanf(buf,"%d",&i); /* do the work and output the result */ printf("press RETURN to continue\n"); getchar(); return 0; }

This piece of code works properly and pauses the program.


142

With sscanf, it is also easier to handle input errors. The following example illustrates how this can be done: #include <stdio.h > int main() { int a,b; char buf[80]; int flag; int nval; do { printf("Enter a and b >"); gets(buf); nval=sscanf(buf,"%d %d",&a,&b); if(nval!=2) { flag=1; puts("Error - start again"); } else { flag=0; } } while(flag==1); printf("%d %d\n",a,b); getchar(); return 0; }

This program will not accept input such as: Enter a and b >1 <RETURN> Enter a and b >1 string <RETURN> Enter a and b >1.5 2 <RETURN>

It is not, however, a totally fool-proof method: Enter a and b>1 4.5<RETURN> is accepted. a and b are assigned with the values 1 and 4 respectively.

143

12.4 File Access: Opening and Closing a File


The C standard also provides functions to handle files. First of all, a file has to be opened before it can be used. This is done using the library function fopen. When a file is opened, it is associated with a stream and fopen returns a pointer. This pointer, called a file pointer, points to a structure that contains information about the file, most importantly, the position in the file where the next data is to be read or written. This file structure is defined in <stdio.h> under the name FILE, thanks to a typedef statement. For example: int main() { FILE *fp; ... fp=fopen(name,mode); return 0; }

name is a string which contains the name of the file to be opened. mode is also a string which indicates how the file is to be used. Permissible modes are: "r" "w" "a" open text file for reading create text file for writing append mode: open or create text file for writing at the end of file

"r+" open text file for update. (both reading and writing) "w+" create text file for update. "a+" open or create text file for update or writing at end of file.

If binary files are handled, a "b" has to be added at the end.

144

fopen returns a NULL pointer if the file has not be opened successfully. After use, a file has to be close using the fclose function. For example:

int main() { FILE *fp; ... fp=fopen(name,mode); ... fclose(fp); ... return 0; }

12.4.1 Reading Text Files

There are three basic ways to read and write text files: 1.According to a format specification 2.A character at a time 3.A line at a time.

Reading a text file according to a format specification can be done using fscanf. fscanf reads text data from a file and converts them into binary values. fscanf is a close relative of scanf. Its prototype is: int fscanf(FILE*stream,const char *format,...)

Below is an example using fscanf to read several floating-point values stored in a text file:

145

#include <stdio.h > #include <stdlib.h > int main() { FILE *inpf; int i, count; double *array; /* open file */ inpf = fopen("TEST.DAT", "r"); if (!inpf) { puts("Can't open TEST.DAT"); return 1; } /* read count of values */ fscanf(inpf, "%d", &count); if (count <= 0) { printf("Bad count value of %d\n", count); return 1; } /* create array and read values from file */ printf("\n Creating array of %d values\n", count); array = (double *)malloc(count * sizeof(float)); for (i = 0; i < count; i++) fscanf(inpf, "%lf", &array[i]); fclose(inpf); /* display array of values */ for (i = 0; i < count; i++) printf("array[%2d] == %lf\n", i, array[i]); free(array); /* free memory allocated to array} */ return 0; }

146

The function fgetc can be used to read a single character from a file. fgetc returns the character it has just read. Its prototype is: int fgetc(FILE *stream)

For example: The program below reads a text file one character at a time and displays it on the screen. #include <stdio.h > #include <stdlib.h > int main() { FILE *fp; char c,filename[80]; printf("Enter the file name:"); gets(filename); fp = fopen(filename, "r"); if (!fp) Error("Opening file"); while ((c = fgetc(fp)) != EOF) putchar(c); fclose(fp); return 0; }

Note that getchar could be implemented as a macro using fgetc: #define getchar() fgetc(stdin)

In fact, getchar is implemented using getc which is equivalent to fgetc but is a macro rather than a function. The function fgets can be used to read one line of a file. It reads characters until it meets a new-line character, and returns a string containing the character just read. Its prototype is: char *gets(char *s);

147

For example: the program performs the same task as the previous example but the text file is read line by line:

#include <stdio.h > #include <stdlib.h > int main() { FILE *fp; char buffer[256]; printf("Enter the file name:"); gets(filename); fp = fopen(argv[1], "r"); if (!fp) Error("Opening file"); while (fgets(buffer, 255, fp) != NULL) fputs(buffer, stdout); fclose(fp); return 0; }

Function fgets requires three arguments: 1.the address of a variable to hold a line of text, 2.the maximum number of characters to read, and 3.a FILE * variable, previously opened with fopen() for any reading mode.

When fgets() reads a line from an open file, it appends a terminating null. The function also places a newline character, '\n' into the destination buffer if that character is read from the file.

148

12.4.2 Writing Text Files


Writing text files is similar to reading them. To create a new text file, declare a FILE * variable named fp (or another name) and open it with the statement: fp = fopen("NEWFILE.TXT", "w"); Mode "w" creates a new file, overwriting an existing file of the same name.

To open an existing file for reading and writing, the mode "r+" is used. This does not overwrite an existing file. After calling fopen, if fp is not null, the new file is open and ready to receive characters. For example: fputc('A', fp); fputc('B', fp); fputc('C', fp);

To write lines of text use fputs: fputs("write to disk please", fp);

Unlike puts, fputs does not append a carriage return and line feed to the output. Hence it is necessary to follow fputs with a call to fputc and a single newline character as an argument: fputc('\n', fp);

To write formatted text to files, use the function fprintf. This works in exactly the same way as printf() but requires a FILE * variable as the initial argument. For example, the line below writes a double value d formatted in eight columns and two decimal places: fprintf(fp, "%8.2f", d);

149

Another useful function is sprintf(), which is a string version of printf(). Declare a buffer to hold sprintf()'s result, then call it as below: char buffer[256]; /* Buffer to hold result */ sprintf(buffer, "%8.2f", d);

Assuming d is a double variable, this deposits in buffer a null-terminated string representation of d's value.

150

Program 1
/*----------------------------------------------------------------FUNCTION: lllustration of the fact that parameters are passed by value in C, not reference. Uses swap function for swapping two integers to demonstrate the point. Values remain the same in main after calling the swap function. ---------------------------------------------------------------*/

#include <stdio.h> void swap(int x, int y);

int main() { int x, y; x = 0; y = 1; printf("in main before swap: x = %d, y=%d\n", x, y); swap(x, y); printf("in main after swap: x = %d, y=%d\n", x, y); Return 0; } void swap(int x, int y) { int temp; printf("in swap at start, x = %d and y = %d\n", x, y); temp = x; x = y; y = temp; printf("in swap at end, x = %d and y = %d\n", x, y); }

151

Program 2
/*--------------------------------------------------------FUNCTION: lllustration of the concept of a pointer in C. Demonstrates the fact that changing the value stored at a pointers address also changes a the value stored in a variable whose address is set equal to the pointers address. --------------------------------------------------------*/

#include <stdio.h> int main() { char letter, *p; char character; p = &letter; letter = 'A'; printf("letter = %c, *p =%c\n", letter, *p); *p = 'B'; printf("letter = %c, *p =%c\n", letter, *p); p = &character; *p = 'Z'; printf("letter=%c,*p=%c,character=%c\n",letter,*p, character); return 0; }

152

Program 3
/*-----------------------------------------------------------------FUNCTION: lllustrates how parameter passing by reference (as in FORTRAN, for example) can be simulated by passing pointers to variables across rather than the variables themselves. Rewrites the previous swap function using pointers. The program now behaves as expected. -------------------------------------------------------------------*/

#include <stdio.h> void swap(int *p_x, int *p_y);

int main() { int x, y; x = 0; y = 1; printf("in main before swap: x = %d, y=%d\n", x, y); swap(&x, &y); printf("in main after swap: x = %d, y=%d\n", x, y); return 0; } void swap(int *p_x, int *p_y) { int temp; printf("in swap at start, x=%d and y=%d\n",*p_x,*p_y); temp = *p_x; *p_x = *p_y; *p_y = temp; printf("in swap at end, x=%d and y=%d\n",*p_x,*p_y); }

153

Program 4
/*-----------------------------------------------------------------FUNCTION: lllustrates the use of pointers to return values from functions. The return statement can only return one value, hence if it is necessary to return more than one value pointers can be used and the function can be declared void. The function computes the equation of a line given two points on the line. It returns the intercept (b) and the slope (m) (y = mx + b). ----------------------------------------------------------------*/

#include <stdio.h> #include <stdlib.h> typedef struct point { float x, y; } point; int line(point pt1, point pt2, float *p_M, float *p_B); int main() { point pt1, pt2; double M, B; printf("Input x, y for point 1:\n"); scanf("%lf %lf", &pt1.x, &pt1.y); printf("Input x, y for point 2:\n"); scanf("%lf %lf", &pt2.x, &pt2.y); printf("pt1(%lf,%lf) pt2(%lf,%lf)\n", pt1.x, pt1.y, pt2.x, pt2.y); if (line(pt1, pt2, &M, &B) == -1) { printf("Error in computing equation. Exiting program\n"); return 1; } else { printf("Y = %lf X + %lf\n", M, B); return 0; } } int line(point pt1, point pt2, double *p_M, double *p_B) { if (pt1.x == pt2.x) return -1; else { *p_M = (pt2.y - pt1.y) / (pt2.x - pt1.x); *p_B = pt1.y - *p_M * pt1.x; return 0; } }

154

Program 5
/*-----------------------------------------------------------------FUNCTION: lllustrates the fact that arrays and pointers are closely connected. The array name is a pointer that points to the first element of the array. Hence arrays can be passed by reference across functions by using their name. Uses a simple swap function to demonstrate this principle. -------------------------------------------------------------------*/

#include <stdio.h> void arrayswap(char *X, int size); int main() { char A[2]; char B[4]; A[0]='a'; A[1]='b'; printf("Before arrayswap %c %c\n",A[0], A[1]); arrayswap(A, 2); printf("After arrayswap %c %c\n",A[0], A[1]); B[0] = 'c'; B[3] = 'd'; printf("Before arrayswap %c %c\n",B[0], B[3]); arrayswap(B, 4); printf("After arrayswap %c %c\n",B[0], B[3]); return 0; } void arrayswap(char X[], int size) { char temp; temp = X[0]; X[0] = X[size-1]; X[size-1] = temp; }

155

Program 6
/*-------------------------------------------------FUNCTION: A simple function to find the min and max of a set of integers using an iterative approach. -------------------------------------------------*/

#include <stdio.h> #define NP 20 void minmax(int numberlist[], int n, int *p_min, int *p_max); int main() { FILE *fp1; int k, numint, list[NP], min, max; if ((fp1 = fopen("minmax.dat", "r")) == NULL) { printf("Data file minmax.dat not found\n"); return 1; } fscanf(fp1,"%d ",&numint); for (k=0; k<=numint-1; k++) fscanf(fp1,"%d ",&list[k]); minmax(list, numint, &min, &max); printf("The min amd max are %d %d\n", min, max); fclose(fp1); return 0; } void minmax(int a[], int n, int *p_min, int *p_max) { int i; *p_max = *p_min = a[0]; for (i = 1; i < n; i++) { if (a[i] > *p_max) *p_max = a[i]; if (a[i] < *p_min) *p_min = a[i]; } }

156

Program 7
/*-----------------------------------------------------------------FUNCTION: Implementation of the 'towers of hanoi' problem. Uses a recursive approach leading to very compact code. -------------------------------------------------------------------*/

#include <stdio.h> void hanoi(char from, char to, char other, int n); int main() { int n; printf("Input the number of disks: "); scanf("%d", &n); if (n <= 0) { printf("Number not allowed\n"); exit(-1); } else { hanoi('a', 'c', 'b', n); exit(0); } return 0; } void hanoi(char from, char to, char { if (n == 1) printf("Move disk from else { hanoi(from, other, to, hanoi(from, to, other, hanoi(other, to, from, } } other, int n)

%c to %c\n", from, to); n-1); 1); n-1);

157

Program 8
/*-----------------------------------------------------------------FUNCTION: lllustration of the dynamic memory allocation function malloc.Allows determination of amount of memory for a given problem to be determined at run time, allowing for efficient code. -------------------------------------------------------------------*/

#include <stdio.h> int main() { long *p_Array; int i, count; printf("How many integers? "); scanf("%d", &count); p_Array = (long *) malloc(count * sizeof(long)); if (p_Array == NULL) { printf("Could not allocate the space."); exit(1); } printf("Enter %d integers.\n", count); for (i = 0; i < count; i++) { scanf("%d", &p_Array[i]); } /* or */ /* for (i = 0; i < count; i++) { scanf("%d", p_Array + i); } */ /* ... ... */ free(p_Array); }

158

Program 9
/*-----------------------------------------------------------------FUNCTION: Function to find the min and max of a set of integers but this time using a recursive approach. Divides the list into two and recursively finds the min and max of each half. -------------------------------------------------------------------*/

#include <stdio.h> #define NP 20 void minmax(int numberlist[], int n, int *p_min, int *p_max); int main() { FILE *fp1; int k, numint, list[NP], min, max; if ((fp1 = fopen("minmax.dat", "r")) == NULL) { printf("Data file minmax.dat not found\n"); exit(1); } fscanf(fp1, "%d ",&numint); for (k=0; k<=numint-1; k++) fscanf(fp1,"%d ",&list[k]); minmax(list, numint, &min, &max); printf("The min amd max are %d %d\n", min, max); fclose(fp1); return 0; } void minmax(int numberlist[], int n, int *p_min, int *p_max) { int min2, max2; if (n == 1) *p_min = *p_max = numberlist[0]; else if (n == 2) { if (numberlist[0] < numberlist[1]) { *p_min = numberlist[0]; *p_max = numberlist[1]; } else { *p_min = numberlist[1]; *p_max = numberlist[0]; } } else { minmax(numberlist, n/2, p_min, p_max); minmax(numberlist + n/2, n - (n/2), &min2, &max2); if (min2 < *p_min) *p_min = min2; if (max2 > *p_max) *p_max = max2; } }

159

Program 10
/*-----------------------------------------------------------------FUNCTION: Illustrates the use of arrays of pointers, where each element in the array points to real data located elsewhere. Here the example keeps track of a series of strings located from a text file. -------------------------------------------------------------------*/

#include <stdio.h> #include <stdlib.h> #define MAX 3 char *ReadString(void); int main() { int i,n; /* array index */ char *array[MAX]; /* array of MAX char pointers */ printf("Enter %d strings: \n", MAX); for (i=0; i < MAX; i++) { array[i] = ReadString(); if (array[i] == NULL) { puts("Error allocating string"); exit(1); } } puts("\n\n Your strings are: \n"); for (i=0; i < MAX; i++) puts(array[i]); return(0); } char *ReadString(void) { char *p; /* p is a pointer to a char array */ char buffer[]; /* buffer for reading each string */ gets(buffer); p = (char *)malloc(1 + strlen(buffer)); if (p) strcpy(p, buffer); return p; } /* maximum number of strings */

160

Program 11
/*-----------------------------------------------------------------FUNCTION: Illustrates the concept of a pointer to an array of pointers through the command line arguments argc and argv. Here argv is a pointer that addresses an array of char pointers(i.e strings). The function simply outputs the elements of this array as read in from the command line. -------------------------------------------------------------------*/

#include <stdio.h> int main(int argc, char *argv[]) { if (argc <= 1) { puts(" "); puts("Enter command line to test"); puts("command line arguments."); } else while (--argc > 0) puts(*++argv); return 0; }

Program 12
/*-----------------------------------------------------------------FUNCTION: Example of the pointer to a pointer concept and the double dereferencing (**) to obtain the value stored. -------------------------------------------------------------------*/

#include <stdio.h> int main() { double d; double *dp; double **dpp;

/* /* /*

a double variable */ pointer to a double */ indirect pointer to a double */

d = 3.14159; dp = &d; dpp = &dp; printf("value of d == %f\n", d); printf("value of *dp == %f\n", *dp); printf("value of **ppd == %f\n", **dpp); return(0); }

161

Program 13
/*-----------------------------------------------------------------FUNCTION: Illustrates the method of passing a pointer to a pointer as a parameter to a function. Uses double dereferencing to obtain the value stored. -------------------------------------------------------------------*/

#include <stdio.h> void Assign(double **dpp); int main() { double d; double *dp;

/* /*

a double variable */ pointer to a double */

d = 0; dp = &d; Assign(&dp); printf("value of d == %f\n", d); printf("value of *dp == %f\n", *dp); return 0; } void Assign(double **dpp) { **dpp = 3.14159; }

162

Program 14
/*-----------------------------------------------------------------FUNCTION: Illustrates the use of pointers to structures and pointers to structure pointers. -------------------------------------------------------------------*

#include <stdio.h> typedef struct item { int a; int b; } Item; typedef Item *Itemptr; typedef Itemptr *Itemref; int main() { Item i; Itemptr ip; Itemref ir; i.a = 1; i.b = 2; ip = &i; ir = &ip; printf("value of i.a == %d\n", (*ir)->a); printf("value of i.b == %d\n", (*ir)->b); return 0; }

163

Program 15
/*-----------------------------------------------------------FUNCTION: Illustrates how to pass a pointer to a pointer to a structure=(structure reference) to a function parameter. -------------------------------------------------------------*/

#include <stdio.h> typedef struct item { int a; int b; } Item; typedef Item *Itemptr; typedef Itemptr *Itemref; void Assign(Itemref ir); int main() { Item i; Itemptr ip;

/* an item structure */ /* pointer to an item struct */

i.a = 1; i.b = 2; ip = &i; Assign(&ip); printf("value of i.a == %d\n", ip->a); printf("value of i.b == %d\n", ip->b); return 0; } void Assign(Itemref ir) { (*ir)->a = 4; (*ir)->b = 5; }

164

Program 16
/*-----------------------------------------------------------------FUNCTION: Illustrates the use of pointers as function results. The example used here is one to convert lower case to upper case. -------------------------------------------------------------------*/

#include <stdio.h> #include <ctype.h> #include <string.h> char *Uppercase(char *s); int main() { char title[] = "capital letter conversion"; char *cp; cp = Uppercase(title); puts(cp); return 0; } char *Uppercase(char *s) { int i; for (i = 0; i < strlen(s); i++) s[i] = toupper(s[i]); return s; /* return address of argument */ }

165

Program 17
/*-----------------------------------------------------------------FUNCTION: An example of the allocation of dynamic variables on the heap using the memory allocation function malloc. -------------------------------------------------------------------*/

#include <stdio.h> #include <stdlib.h> #define SIZE 64 void *MakeBlock(unsigned size, char init); int main() { void *p = NULL; p = MakeBlock(SIZE, 0xff); /* Block of SIZE 0xFF bytes */ free(p); return 0; } void *MakeBlock(unsigned size, char init) { void *p; int i; p = (void *)malloc(size); if (p) { for (i = 0; i < size; i++) ((char *)p)[i] = init; } return p; }

166

Program 18
/*-----------------------------------------------------------------FUNCTION: Illustrates the use of pointers to functions, that is pointers that address executable code. The example used here is to plot mathematical functions. The graphics routine has not been included here. -------------------------------------------------------------------*/

#include <stdio.h> #include <math.h> #define XSCALE 20 #define YSCALE 10 #define XMIN 1 #define XMAX 79 #define YMIN 1 #define YMAX 25 typedef double (* Pfptr)(int x); void Yplot(int x, double f); double Afunction(int x); int main() { int x; Pfptr pf = Afunction; for (x = XMIN; x <= XMAX; x++) Yplot(x, (* pf)(x * XSCALE); .... return 0; } void Yplot(int x, double f) { /* graphic routine for plotting a point */ } double Afunction(int x) { return sin(x); }

167

Program 19
/*-----------------------------------------------------------------FUNCTION: Illustrates the use of the standard IO functions in C. The example is to read a text file one character at a time. -------------------------------------------------------------------*/

#include <stdio.h> #include <stdlib.h> void Error(const char *message); void Instruct(void); int main(int argc, char *argv[]) { FILE *fp; char c; if (argc <= 1) Instruct(); fp = fopen(argv[1], "r"); if (!fp) Error("Opening file"); while ((c = fgetc(fp)) != EOF) putchar(c); fclose(fp); return 0; } void Error(const char *message) { printf("\n\n Error: %s\n\n", message); } void Instruct(void) { puts("\n Syntax: RCHAR filename"); puts("Enter the name of a text file."); }

168

Program 20
/*-----------------------------------------------------------------FUNCTION: Example illustrating the use of the fscanf function for reading values from text files as binary representations. Here several floating point values are read from a text file. -------------------------------------------------------------------*/

#include <stdio.h> #include <stdlib.h>

void main() { FILE *inpf; int i, count; double *array; /* open file */ inpf = fopen("TEST.DAT", "r"); if (!inpf) { puts("Can't open TEST.DAT"); exit(1); } /* read count of values */ fscanf(inpf, "%d", &count); if (count <= 0) { printf("Bad count value of %d\n", count); exit(2); } /* create array and read values from file */ printf("\n Creating array of %d values\n", count); array = (double *)malloc(count * sizeof(float)); for (i = 0; i < count; i++) fscanf(inpf, "%lf", &array[i]); fclose(inpf); /* display array of values */ for (i = 0; i < count; i++) printf("array[%2d] == %lf\n", i, array[i]); free(array); /* free memory allocated to array */ return 0; }

169

Program 21
/*-----------------------------------------------------------------FUNCTION: Example illustrating how to write data sequentially to a binary file. The function writes an array of integers to a binary file. -----------------------------------------------------------*/

#include <stdio.h> #include <stdlib.h>

void main() { FILE *outf; int i; outf = fopen("INT.DAT", "wb"); if (!outf) { puts("Cannot create INT.DAT"); exit(1); } puts("Writing 100 integer values to INT.DAT"); for (i = 0; i < 100; i++) fwrite(&i, sizeof(int), 1, outf); fclose(outf); return 0; }

170

Program 22
/*-----------------------------------------------------------------FUNCTION: Example illustrating how to read binary files. The program reads binary values from a file one a time. -------------------------------------------------------------------*/

#include <stdio.h> #include <stdlib.h> int main() { FILE *inpf; int i, value; inpf = fopen("INT.DAT", "rb"); if (!inpf) { puts("Cannot open INT.DAT"); exit(1); } for (i = 0; i < 100; i++) fread(&value, sizeof(int), 1, inpf); /* to read more that one value at a time, e.g. all values at once the for loop can be replaced with the statement */ /* fread(&array, sizeof(int), 100, inpf); */ printf("%8d", value); return 0; }

171

Program 23
/*-----------------------------------------------------------------FUNCTION: Illustrates the use of random access files. The program uses the function fseek() to move the file's internal pointer to the desired location. Then fread() is used to load the required value. -------------------------------------------------------------------*/

#include <stdio.h> #include <stdlib.h> int main() { FILE *inpf; int value; inpf = fopen("INT.DAT", "rb"); if (!inpf) { puts("Can't open INT.DAT"); exit(1); } fseek(inpf, 10 * sizeof(int), SEEK_SET); fread(&value, sizeof(int), 1, inpf); printf("Record #10 == %d\n", value); fclose(inpf); return 0; }

172

Program 24
/*-----------------------------------------------------------------FUNCTION: Illustrates the use of random access files. The program demonstrates how to position the file pointer and call fwrite() to write a value at random to the file without affecting the other values stored there. -----------------------------------------------------------------*/

#include <stdio.h> #include <stdlib.h> int main() { FILE *outf; int value = 99;

/*

new value to write */

outf = fopen("INT.DAT", "r+b"); if (!outf) { puts("Can't open INT.DAT"); exit(1); } printf("Writing %d to record #10\n", value); fseek(outf, 10 * sizeof(int), SEEK_SET); fwrite(&value, sizeof(int), 1, outf); fclose(outf); return 0; }

173

S-ar putea să vă placă și