CHAPTER 5

advertisement
CHAPTER 5
Functions and Program Structure I
A Gentle Introduction
Reference: Brooks, Chapter 5





Up until now, our programs consisted of only one function: main()
This was fine so far as our programs have been very small
In real life, programs will grow to be very large
It is therefore necessary to break the program up into a number of smaller units
Already, without thinking about it, our programs have used a number of
predefined functions such as printf and scanf as well as some mathematical
functions such as sin and cos
 It is appropriate now for us to learn how to write our own functions




Consider the problem of writing a program to compute the volume of a cylinder
The volume is the area of the base (which is a circle) times the height
We can implement this by writing one function to compute an area of a circle
We then write a function to compute the volume by first computing the area of
the base and then multiplying by the height
 Our main program can call the second function to compute the volume
main
cylinder_volume
circle_area
Copyright (c) 1999 by Robert C. Carden IV, Ph.D.
2/5/2016
Functions and Program Structure I
A Gentle Introduction - The Program
#include <stdio.h>
const double PI = 3.14159265;
double circle_area (double radius)
{
double area = PI * (radius * radius);
return area;
}
double cylinder_volume (double radius, double height)
{
double area = circle_area (radius);
double volume = area * height;
/* return circle_area (radius) * height; */
return volume;
}
int main (void)
{
int result;
double radius;
double height;
double area;
double volume;
printf ("%s",
"This program computes the volume"
" of a cylinder and the area of its base.\n"
"Enter radius of the base: ");
result = scanf ("%lf", &radius);
printf ("Enter height of cylinder: ");
result += scanf ("%lf", &height);
if (result != 2)
{
printf ("Input error\n");
return 1;
}
area = circle_area (radius);
volume = cylinder_volume (radius, height);
printf ("Radius %.2f, height %.2f: area=%.2f, volume=%.2f\n",
radius, height, area, volume);
return 0;
}
5-2
Functions and Program Structure I
Dissection of the Cylinder Program - External Variables
#include <stdio.h>
const double PI = 3.14159265;
 The program starts out by #including <stdio.h>
 It does this to include the declarations of functions such as scanf() and
printf()
 Inside this file, we see such function declarations:
int printf(const char *, ...);
int scanf(const char *, ...);
 Below that, outside of any function, we define the variable PI
 This variable is accessible by any function that appears below it
 This is in contrast to variables declared inside of functions
 Such variables are visible from the point of declaration down to the end of that
function
 The variable PI is said to have global scope
 Variables declared inside of functions are said to have local scope
5-3
Functions and Program Structure I
Dissection of the Cylinder Program - A simple function
double circle_area (double radius)
{
double area = PI * (radius * radius);
return area;
}
 The first line is the function header
 The syntax for a function header is
return_type function_name (type1 parm1, type2 parm2, ..., typeN parmN)





function_name is the name given to the function
type is the data type of the value that the function returns
type1 is the data type of the first parameter, parm1
type2 is the data type of the second parameter, parm2
typeN is the data type of the Nth (last) parameter, parmN
 The function body starts with the opening brace { and ends with the closing
brace }
 The syntax of a function body is:
{
declarations
statements
}
 This function has a single declaration (that of area)
 It has two statements: a declaration with an expression and a return statement
 a declaration with an expression
 a return statement
 The return statement associates a return value with the function
 It also terminates the function, i.e. we return to the caller the desired result
5-4
Functions and Program Structure I
Dissection of the Cylinder Program - A function calling a function
double circle_area (double radius)
{
double area = PI * (radius * radius);
return area;
}
double cylinder_volume (double radius, double height)
{
double area;
double volume;
area = circle_area (radius);
volume = area * height;
return volume;
}
 We first implement a general purpose function to compute the area of a circle
 All it requires it the radius of the circle
 With that, it computes and returns the area
 The second function computes the volume of a cylinder by first computing the
area of the base
 It does this by calling our first function
 Notice that each function declares a local variable area
 Also, each function has a parameter called radius
 Because of the scoping rules, each declaration is separate and distinct from one
another
5-5
Functions and Program Structure I
Dissection of the Cylinder Program - the main() function
int main (void)
{
int ret;
double rad, height, a, volume;
printf ("%s %s\n%s",
"This program computes the volume",
"of a cylinder and the area of its base.",
"Enter radius of the base: ");
ret = scanf ("%lf", &rad);
printf ("Enter height of cylinder: ");
ret += scanf ("%lf", &height);
if (ret != 2)
{
printf ("Input error\n");
return 1;
}
a = circle_area (rad);
volume = cylinder_volume (rad, height);
printf("Radius %.2f, height %.2f: area=%.2f, volume=%.2f\n",
rad, height, a, volume);
return(0);
}
 The main program calls circle_area and cylinder_volume to compute the area
and volume of the base and cylinder respectively
main
cylinder_volume
circle_area
5-6
Functions and Program Structure I
Function invocation and call by value
Function invocation means the following:
1. Each expression in the argument list is evaluated (no particular order).
2. The value of the expression is converted, if necessary, to the type of the formal
parameter, and that value is assigned to its corresponding formal parameter at
the beginning of the body of the function.
3. The body of the function is executed.
4. If a return statement is executed, then control is passed back to the calling
environment.
5. If the return statement includes an expression, then the value of the expression
is converted, if necessary, to the type given by the type specifier of the function,
and that value is passed back to the calling environment too.
6. If the return statement does not include an expression, then no useful value is
returned to the calling environment.
7. If no return statement is present, then control is passed back to the calling
environment when the end of the body of the function is reached. No useful
value is returned.
8. All arguments (except arrays) are passed call by value.
int main(void)
{
...
volume = cylinder_volume (rad, height);
...
}
double cylinder_volume (double radius, double height)
{
double volume;
...
...
return volume;
}
5-7
Functions and Program Structure I
Function invocation and call by value (2)
main program
compute sum
#include <stdio.h>
/* should be in a header file. */
int compute_sum (int);
int main (void)
{
int n = 3, sum;
/*sum integers from 1
to n*/
int compute_sum (int n)
{
int sum = 0;
/* stored value of n
* is changed
*/
for ( ; n > 0; --n)
sum += n;
return sum;
printf ("%d\n",n);
/* 3 */
sum = compute_sum (n);
printf ("%d\n", n);
/* 3 */
printf ("%d\n", sum); /* 6 */
}







}
This example illustrates what it means when we say the C uses call by value
Notice that compute_sum changes the value of parameter n internally
However, this has no effect to the calling procedure
Thus, after calling compute_sum(n) in main(), n remains 3
This is because the argument is copied over
C has no call by reference (or VAR parameters as in Pascal)
C++ extends C by adding call by reference parameters
To return information back to the calling routine in C, one must pass in a
pointer (more later)
5-8
Functions and Program Structure I
Pointers and function arguments


C passes in arguments to functions by value, i.e. it copies the parameters over
The following is a wrong implementation of a function to swap two integers
/*WRONG*/
void swap (int x, int y)
{
int tmp = x;
x = y;
y = tmp;
}
void foo (void)
{
int a = 10, b = 20;
swap (a, b);
printf ("a=%d, b=%d\n", a, b);
}
Initial activation frame
tmp
b
20
a
10
call swap
5-9
x
10
y
20
b
20
a
10
Functions and Program Structure I
Activation frame after swap completes and exits
tmp 10



x
20
y
10
b
20
a
10
swap exits
b
20
a
10
No change
to a or b!
This implementation of swap fails because it failed to take into account that the
parameters are copies of the inputs
A correct implementation needs references to the objects being swapped to be
passed in
We now reimplement swap by passing in pointers to the objects to be swapped
void swap (int *x, int *y)
{
int tmp = *x;
*x = *y;
*y = tmp;
}
void foo (void)
{
int a = 10, b = 20;
swap (&a, &b);
}
5-10
Functions and Program Structure I
Activation frames of call to swap
tmp
x
y
Initial
stack
b
20
a
10
call swap
tmp 10
tmp = *x
b
20
a
10
tmp 10
x
x
y
y
b
20
a
10
*x = *y
b
20
a
20
b
10
a
20
tmp 10
x
y
*y = tmp
b
10
a
20
exit swap
success
5-11
Functions and Program Structure I
Application -- the scanf function




The standard library function scanf() may be used to read in input from the
terminal
Like printf(), it expects a format string followed by a series of arguments
In many ways, the format is identical to printf
However, because scanf expects a pointer to its argument, there are some
areas where it is distinctly different
format
%d
%u
%hd
%ld
%f
%lf
%Lf
%c
%s
object
required variable type
int *
unsigned int *
short *
long *
float *
double *
long double *
char *
char *
Int
Unsigned int
Short
Long
Float
Double
Long double
Character
Char array
example
void foo (void)
{
int i;
scanf("%d", &i);
format string specifies that i is int *
scanf copied integer over
to *(&i), i.e. i
}
5-12
Functions and Program Structure I
Application -- the scanf function (2)
void fubar (void)
{
int *ip;
scanf("%d", ip);
Format string specifies that ip is int *
Program will probably crash when scanf
tries to write output into *ip
The problem is that pointer ip is
uninitialized when passed to scanf
}



The problem encountered in this example is that an invalid pointer is passed to
scanf
Because ip was never initialized to point to anything valid, it is currently
"pointing into space"
Programmers must make certain that pointers are always pointing to something
valid before used
Void bar (void)
{
int x, *ip = &x;
scanf("%d", ip);
Specifies that ip is int *
Legal -- x will contain the new
value because this is what ip
is pointing to
}
5-13
Functions and Program Structure I
Printf versus scanf


The format string for functions printf and scanf are similar in many ways
However, one may observe that scanf is much pickier about its arguments
Format



object
%d
int
%hd
short
%ld
%f
long
float
%lf
double
%Lf
%g
long double
floating point
%g
float
allowable types
for printf
char
short
int
char
short
int
long
float
double
float
double
long double
float
double
N/A
allowable types for
scanf
int *
short *
long *
float *
double *
long double *
N/A
float *
Why this subtle difference in parameter requirements?
 printf and scanf both use the stack up approach for the additional
arguments
 this is because the additional arguments (the ones specified by the format)
fall under the ellipsis of the function prototype
The printf function takes advantage of the fact that
 char and short both promote to int
 float promotes to double
However, scanf cannot take advantage of this because a pointer remains
unchanged
5-14
Functions and Program Structure I
Printf versus scanf (2)
Void foo (void)
{
int x;
scanf("%d", &x);
Specifies that &x is int *
Legal -- x will contain new value
printf("x = %d\n", x)
Specifies that x is int -- okay
scanf("%hd", &x);
/*trouble*/
Specifies that &x is short *
Will work if shorts and ints are the
same size
Will fail on a machine where shorts
are different in size from ints,
e.g. 16 and 32 bits respectively
as is the case on an Apollo DN3000
printf("x = %hd\n", x)
Specifies that x is short
Printf expects to find an int because
it knows that its parameter, even if
it is short, will promote to int.
The input is then treated (converted)
as if it were a short
}
5-15
Functions and Program Structure I
Printf versus scanf (3)

Pointers get passed in without change
Void foo2()
{
float f; double d;
scanf("%f", &f);
scanf expects float *
printf("f = %g\n", f)
printf expects double
The float argument gets promoted
scanf("%g", &d);
/*trouble*/
scanf expects float * buts gets a
double * argument
On most machines, doubles are much
larger than floats so this will
probably not work
printf("d = %g\n", d)
printf expects double
scanf("%lf", &d);
/*correct*/
scanf expects double *
}
5-16
Functions and Program Structure I
Traditional C syntax



The following subsection will describe function definitions and declarations in
traditional C
This is not to be confused with the approach we have used thus far, that is ANSI
C style
In traditional C one defines functions by using the following general syntax
return-type function-name ( argument-list )
argument-declarations
{
declarations and statements
}


Notice that there is no semicolon after the closing parenthesis
To declare a function in C which will be defined at a later time, one uses the
following syntax
return-type function-name ( ) ;


return-type is optional
 if omitted, function assumed to return int
 in the original 1970's version of C, functions could only return and/or have
scalar parameters (integer, floating point, pointers)
 in traditional C, anything may be passed in and/or returned
argument may be in argument-list but not declared in the declaration-list
 such an argument defaults to int
5-17
Functions and Program Structure I
Functions in traditional C (2)
do_something (a, b, c)
float b;
short c;
{
...
}



The return value of function do_something is int
The parameter a is assumed to be an int
parameters b and c are float and short respectively
void bad_decl (a, b)
int a;
float b;
double c;
{
...
}



the return value of the function is void
syntax error: parameter c is declared but not mentioned in the argument list
If one calls a function which has not been declared previously, C assumes that it
returns int and that it may have 0 or more arguments of any type
C code fragment
Assumptions
int a, b, c;
...
foo (a, b, c); int foo ()
c = fubar (a); int fubar ()


Generally, it is good practice to declare all functions, even if they return int
(though most traditional C programmers do not)
Also, it is good practice to declare all function arguments and return types, even
if the default value of int is what you want
5-18
Functions and Program Structure I
Functions in traditional C (3)
traditional C approach to parameter passing


Stack them up -- pop them off strategy
Arguments are pushed onto the stack in reverse order (as compared to Pascal)
void foo (a, b, c)
int a;
float b;
char c;
{
...
}
void bar (void)
{
/*1*/ foo (33, 62.0, 'c');
/*2*/ foo ((short) 25, 3.14F, (char) 'a' );
}



This code fragment first defines a function foo using the traditional C style
Because it uses the older format, the compiler assumes that the function will be
declared without a prototype
As a result, when foo is called, the default promotions will be performed
 char, short and int get passed in as int
 long gets passed in as long
 float and double get passed in as double
 long double gets passed in as long double
 everything else gets passed in as is, bit for bit
5-19
Functions and Program Structure I
Functions in traditional C (4)
void foo (a, b, c)
int a;
float b;
char c;
{
...
}
void bar (void)
{
/*1*/ foo (33, 62.0, 'c');
/*2*/ foo ((short) 25, 3.14F, (char) 'a' );
}



The definition of the function foo specifies that it has three parameters, the
first one of type int, the second one of type float, and the third one of type
char.
What this says (because it is written in the traditional C format) is that the first
parameter will in reality be passed in as in int, the second as a double, and the
third one as an int
The following table illustrates what happens from foo's perspective, that is
foo will expect the parameters to be on the stack as follows
Parm
A
B
C
declared as
int
float
char
passed in as
int
double
int
5-20
converted back to
int
float
char
Functions and Program Structure I
Functions in traditional C (5)
void foo (a, b, c)
int a;
float b;
char c;
{
...
}
void bar (void)
{
/*1*/ foo (33, 62.0, 'c');
/*2*/ foo ((short) 25, 3.14F, (char) 'a' );
}











Now, when foo is called, the compiler behaves as if it knows nothing about
what foo expects
It simply promotes the arguments using the same rules
Conceptually, on the first call to foo /*1*/, it is called with parameters 33,
62.0, and 'c' respectively
These constants are of type int, double, and char respectively
Accordingly, the first parameter 33 gets passed in as an int, the second
parameter 62.0 gets passed in as a double, and the third parameter 'c' gets
passed in as an int (widened from char)
On the second call to foo /*2*/, it is called with parameters (short)25,
(float)3.14, and (char)'a'
However, these are simply expressions yielding short, float, and char operands
respectively
The function must still follow the traditional C widening rules
Consequently, 25 will be cast to short but then passed in as an int
Parameter 3.14F is a float constant which will then be widened to double
The third parameter 'a' is cast to char but then passed in (widened) as an int
5-21
Functions and Program Structure I
Functions in traditional C (6)
void foo (a, b, c)
int a;
float b;
char c;
{
...
}
void bar (void)
{
/*1*/ foo (33, 62.0, 'c');
/*2*/ foo ((short) 25, 3.14F, (char) 'a' );
}
The following illustrates the stack after the first call to foo /*1*/
Stack
var
comments
33
62.0
a
b
'c'
c
33 is an int; it is passed in as an int
62.0 is double and therefore passed in as double; because
foo expects a float parameter, it knows to look for a
double on the stack; foo will then convert the double
back to float
'c' is a char but it is widened to int; because foo expects
a char parameter, it knows to look for an int on the
stack; foo will then convert the int back to char
5-22
Functions and Program Structure I
Functions in traditional C (7)
void foo (a, b, c)
int a;
float b;
char c;
{
...
}
void bar (void)
{
/*1*/ foo (33, 62.0, 'c');
/*2*/ foo ((short) 25, 3.14F, (char) 'a' );
}
This is the stack after the second call to foo /*2*/
Stack
var
comments
(short)2
5
a
3.14F
b
(char)'a
'
c
(short)25 is short but is widened to int; because
foo expects an int parameter, it knows to look for
an int on the stack
3.14F is float but it is widened to double; because
foo expects a float parameter, it knows to look
for a double on the stack; foo will then convert
the double back to float
(char)'a' is a char but it is widened to int;
because foo expects a char parameter, it knows
to look for an int on the stack; foo will then
convert the int back to char
5-23
Functions and Program Structure I
Functions in traditional C (8)
void foo (a, b, c)
int a;
float b;
char c;
{
...
}
void bar (void)
{
/*1*/ foo (33, 62.0, 'c');
/*2*/ foo ((short) 25, 3.14F, (char) 'a' );
}






In this example, in traditional C (and also ANSI too because the old style was
defining foo was used and thus no prototype), function bar does not know
what foo expects
Function foo therefore hopes that bar pushed the correct types onto the stack
When function bar calls foo, it pushes the arguments onto the stack
Function foo then pops the stack, demoting arguments as required
Notice that the last argument passed to the function is also at the bottom of the
stack
The first argument passed to the function is at the top of stack
5-24
Functions and Program Structure I
Functions in traditional C (9)
general rule
Char, short, int
Unsigned char
Unsigned short
Unsigned int
Long
Unsigned long
Float, double
Other

pushed onto stack as int; smaller operands are
widened as necessary
pushed onto stack as unsigned int
pushed onto stack as long
pushed onto stack as unsigned long
pushed onto stack as double
pushed onto stack bit for bit (as is)
This allows for functions with a variable number of arguments (such as printf)
DANGER



Argument mismatches may occur
In traditional C, this is a source of many bugs
In the following example, we shall assume that an int is 2 bytes and a long is
4 bytes
void long_ex (a)
long a;
{
...
}




void bad_func (void)
{
...
long_ex (42)
}
In this example, bad_func pushes an int onto the stack
However, long_ex pops off a long
The net result is that bad_func pushed on two bytes when it called long_ex,
but long_ex then turned around and popped four bytes off the stack
This may cause the program to halt abnormally
5-25
Functions and Program Structure I
Functions in ANSI C (1)







ANSI C allows "old-style" declarations and definitions -- this allows old
programs to compile and run with the newer compilers
ANSI C adds a new function definition syntax as well as a new declaration
syntax
The new declaration syntax is often referred as a function prototype
These changes allow users to specify the type and number of the arguments
passed to functions
No arguments specified in the definition implies that the function expects no
arguments -- this may generate a warning
The stack-up rule is followed but arguments are passed to conform with the
prototype
The general syntax is as follows
To define a function
return-type function-name ( argument declarations )
{
declarations and statements
}
To declare a function
return-type function-name ( prototype argument declarations ) ;




The argument names in a declaration are optional
A prototype is the declaration of a function
Prototypes may be given for functions which were defined using the old,
traditional, style of definition
The return-type is optional (as in traditional C) -- defaults to int
5-26
Functions and Program Structure I
Functions in ANSI C (2)


In both traditional and ANSI C, the return statement has the effect of returning
the expression as the value of the function, and then exiting from the function
If the function returns void, then return must be used without an argument
Pascal
C
Function foo (x:integer):integer;
Begin
label 999;
...
foo := x + 1;
go to 999;
...
999:
end;
Procedure bar;
Begin
label 999;
...
go to 999;
...
999:
end;
5-27
int foo (int x)
{
...
return x + 1;
...
}
void bar (void)
{
...
return;
...
}
Functions and Program Structure I
Functions in ANSI C (3)

Consider the following function definition and its declaration and usage
somewhere else
void foo (char c,
float f,
int i)
{
...
}

void bar (void)
{
void foo (char c,
float f,
int i);
...
foo ('a', 22.6, 'b');
...
}
In this example, the stack looks as follows
stack
var
Comments
'a'
c
22.6
f
'b'
i
'a' is a char and the prototype for foo calls for a char;
thus 'a' is pushed onto the stack as a char; foo in turn
pops a char from the stack
22.6 is double and the prototype for foo calls for a float;
thus, 22.6 is converted back to float, pushed onto the
stack as float, and then foo in turn pops a float off the
stack
'b' is a char but it is widened to int because the prototype
for foo calls for an int
5-28
Functions and Program Structure I
Functions in ANSI C (4) -- danger

You may give prototypes for functions using the old style, but you must make
certain that the argument types in the prototype match what the arguments in
the old style function get promoted to
example
void fubar (c, i, l, f, d)
char c;
int i;
long l;
float f;
double d;
{
...
}
wrong prototype
void fubar (char c, int i, long l, float f, double d);
correct prototype
void fubar (int c, int i, long l, double f, double d);



In this example, the first argument (char) gets promoted to int
The fourth argument (float) gets promoted to double
The prototype must reflect this promotion
5-29
Functions and Program Structure I
Functions in ANSI C (5)
The ellipsis





Some functions, such as printf, expect a variable number of arguments.
You can specify this by using the ellipsis in the declaration, i.e. typing ...
Variable argument functions must have at least one other parameter
Macros in <stdarg.h> allow users to get the additional arguments
Traditional C uses <varargs.h> which has a slightly different mechanism
example declaration
int
printf(const char *format,
...);

example implementation
#include <stdarg.h>
int
printf(const char *format,
...)
{
...
}
A function declared:
return-type function-name ();

is assumed to be an old style declaration.
The stack-up approach is followed.

A function declared:
return-type function-name ( void );
accepts no argument (and providing argument should result in a syntax error)


Users must provide exactly the required number of arguments if a prototype is
given except when the user declares it with an ellipsis -- even those must
provide required arguments
Functions with ellipsis use the "stack-up" approach of traditional C for the
optional arguments, i.e. those covered by the ellipsis
5-30
Functions and Program Structure I
Functions in ANSI C (6)
printf format
%c
%d
%ld
%f
%lf
%Lf
expected type
char
int
long
float/double
float/double
long double
printf("%c %d %ld %f %lf %Lf\n",
'c',
/*promoted to int, converted to char*/
22.2,
/*error*/
/*printf expects int but gets double*/
(long)22,
/*correct*/
26.0,
/* printf expects double */
3.14159,
/* printf expects double */
(long double) 26.1
/*must pass in as long double*/
);


ANSI C clears up many problems by allowing users to use prototypes
STYLE: always use prototypes for functions and avoid using “variadic”
parameter lists (i.e., “…”).
5-31
Functions and Program Structure I
Functions in ANSI C (7)
void functions

Recall that the syntax for a return is
return-statement

::=
|
return expression ;
return ;
The second form should (must) be used in void functions
void foo (int x, int y)
{
if (x == 3)
return;
...
if (y > 16)
return;
...
}



The return statement in this example simply exits from the function
Note that a return statement is optional in void functions
Returns are executed simply to exit from the function
5-32
Functions and Program Structure I
Splitting up the cylinder program
 Consider the program we analyzed at the beginning of this chapter
 It consisted of a main() routine which computed the volume of a cylinder
 It called circle_area() to compute the area of the base of the cylinder and
displayed that result
 It also called cylinder_volume() to compute the volume of the cylinder
 Let us consider splitting the program up into three source files
 Each function will be implemented in its own source file
main
cylinder_volume
circle_area
5-33
Functions and Program Structure I
A first stab - three files, three functions
 First, we implement circle_area() and cylinder_volume() in their own source
files
 In Visual Studio, we do a File|New and create these two respective "text files",
calling them area.c and volume.c
 We include them within the same project and let Visual Studio take care of the
rest
/*
* File : area.c
* Author: Robert C. Carden IV
*/
#include <stdio.h>
const double PI = 3.14159265;
double circle_area (double radius)
{
double area = PI * (radius * radius);
return area;
}
/*
* File : volume.c
* Author: Robert C. Carden IV
*/
#include <stdio.h>
double cylinder_volume (double radius, double height)
{
double area = circle_area (radius);
double volume = area * height;
return volume;
}
5-34
Functions and Program Structure I
A first stab - three files, three functions (2)
 We also create a main.c where we place our function main()
/*
* File : main.c
* Author: Robert C. Carden IV
*/
#include <stdio.h>
int main (void)
{
int ret;
double rad, height, a, volume;
printf ("%s %s\n%s",
"This program computes the volume",
"of a cylinder and the area of its base.",
"Enter radius of the base: ");
ret = scanf ("%lf", &rad);
printf ("Enter height of cylinder: ");
ret += scanf ("%lf", &height);
if (ret != 2)
{
printf ("Input error\n");
return 1;
}
a = circle_area (rad);
volume = cylinder_volume (rad, height);
printf ("Radius %.2f, height %.2f: area=%.2f, volume=%.2f\n",
rad, height, a, volume);
return 0;
}
5-35
Functions and Program Structure I
A first stab - three files, three functions (3)
------------------Configuration: area - Win32 Debug------------------Compiling...
area.c
volume.c
H:\class\Ece11-s98\hello\area\volume.c(11) : warning C4013: 'circle_area'
undefined; assuming extern returning int
main.c
H:\class\Ece11-s98\hello\area\main.c(25) : warning C4013: 'circle_area'
undefined; assuming extern returning int
H:\class\Ece11-s98\hello\area\main.c(26) : warning C4013: 'cylinder_volume'
undefined; assuming extern returning int
Linking...
area.exe - 0 error(s), 3 warning(s)
 Notice the warnings that we get
 When we compile volume.c, the compiler complains that circle_area() is
undefined
 Also, it assumes that this function is extern and it returns an int
 We all know, though, that it returns double
 When we compile main.c, the compiler complains that circle_area() and
cylinder_volume() are undefined and assumes that these functions both
return int
 These warnings are worth heeding
 We now execute the program...and get garbage results
5-36
Functions and Program Structure I
The missing link - function declarations
 The problem is that our program is making invalid assumptions about
circle_area() and cylinder_volume()
 We need to include declarations of these functions
 To fix this program, we can declare the functions that are needed in each source
file
/*
* File : volume.c
* Author: Robert C. Carden IV
*/
#include <stdio.h>
double circle_area (double);
double cylinder_volume (double radius, double height)
{
double area = circle_area (radius);
double volume = area * height;
return volume;
}
 At the beginning of our file, we include a declaration (prototype) of the
function we intend to call
 Here we specify that circle_area() expects a double parameter and returns
double
 Pascal calls this type of thing a forward declaration
5-37
Functions and Program Structure I
The missing link - function declarations (2)
/*
* File : main.c
* Author: Robert C. Carden IV
*/
#include <stdio.h>
double circle_area (double);
double cylinder_volume (double, double);
int main (void)
{
int ret;
double rad, height, a, volume;
printf ("%s %s\n%s",
"This program computes the volume",
"of a cylinder and the area of its base.",
"Enter radius of the base: ");
ret = scanf ("%lf", &rad);
printf ("Enter height of cylinder: ");
ret += scanf ("%lf", &height);
if (ret != 2)
{
printf ("Input error\n");
return 1;
}
a = circle_area (rad);
volume = cylinder_volume (rad, height);
printf ("Radius %.2f, height %.2f: area=%.2f,volume=%.2f\n",
rad, height, a, volume);
return 0;
}
 We do the same thing in main(), declaring the two functions we intend to use
5-38
Functions and Program Structure I
The missing link - function declarations (3)
 Compiling it we get no warnings...
-------------------Configuration: area - Win32 Debug-----------------Compiling...
area.c
volume.c
main.c
Linking...
area.exe - 0 error(s), 0 warning(s)
 When we run the program, it now works
5-39
Functions and Program Structure I
Header files - why we need them
 Suppose, however, we got clever and decided that circle_area() only need
receive a float radius
 Also, assume that the only file we change is area.c and we leave the other files
exactly as we had them before
 Compiling we get no warnings or errors; however, it fails to run
/*
* File : area.c
* Author: Robert C. Carden IV
*/
#include <stdio.h>
const double PI = 3.14159265;
double circle_area (float radius)
{
double area = PI * (radius * radius);
return area;
}
--------------------Configuration: area - Win32 Debug-------------------Compiling...
area.c
volume.c
main.c
Linking...
area.exe - 0 error(s), 0 warning(s)
Header files - why we need them (2)
5-40
Functions and Program Structure I
 The trap we fell into was that we had three versions of the truth
 In main.c, we declared circle_area() to expect a double and return double
 In volume.c, we also declared circle_area() to expect a double and return
double
 However, the truth lay in area.c where we defined circle_area() to expect a
float and return double
 This illustrates how the compiler compiles each file separately and individually
with no collective memory of the other files
 C compilers make no effort whatsoever to make sure declarations and
definitions are consistent throughout
 It is the programmer's responsibility to guarantee this
 Because of this, good C programmers follow a convention of putting all
declarations in a separate header file
 Each source file will then #include this header file
 We now add a new file to our project: cylinder.h
/*
* File : cylinder.h
* Author: Robert C. Carden IV
*/
double circle_area (float radius);
double cylinder_volume (double radius, double height);
5-41
Functions and Program Structure I
Header files - why we need them (3)
 We now modify all of the source files: we remove each function declaration and
replace it with the #include
/*
* File : area.c
* Author: Robert C. Carden IV
*/
#include <stdio.h>
#include "cylinder.h"
const double PI = 3.14159265;
double circle_area (float radius)
{
double area = PI * (radius * radius);
return area;
}
/*
* File : volume.c
* Author: Robert C. Carden IV
*/
#include <stdio.h>
#include "cylinder.h"
double cylinder_volume (double radius, double height)
{
double area = circle_area (radius);
double volume = area * height;
return volume;
}
5-42
Functions and Program Structure I
Header files - why we need them (4)
/*
* File : main.c
* Author: Robert C. Carden IV
*/
#include <stdio.h>
#include "cylinder.h"
int main (void)
{
int ret;
double rad, height, a, volume;
printf ("%s %s\n%s",
"This program computes the volume",
"of a cylinder and the area of its base.",
"Enter radius of the base: ");
ret = scanf ("%lf", &rad);
printf ("Enter height of cylinder: ");
ret += scanf ("%lf", &height);
if (ret != 2)
{
printf ("Input error\n");
return 1;
}
a = circle_area (rad);
volume = cylinder_volume (rad, height);
printf ("Radius %.2f, height %.2f: area=%.2f, volume=%.2f\n",
rad, height, a, volume);
return 0;
}
5-43
Functions and Program Structure I
Getting input - centralizing common code
 A common problem people in this class encounter is having to get input from a
user
 Most people use scanf() and hope for the best
 Some people might check the return value of scanf() to see that it returned the
number of arguments that it matched
 Now, let us consider writing a more robust input function
 Rather than duplicating the code throughout, making maintenance impossible,
let us implement such a function in one source file
 By doing so, we are now willing to make the function quite sophisticated since
we will be able to reuse it many times
/*
* File : input.h
* Author: Robert C. Carden IV
*/
/* Give the user supplied prompt and read in
an entire line of input. Scan for a double.
Keep doing so until the user gets it right. */
double input_double (const char * prompt);
/* This global variable is initialized to 0 and
set to a nonzero value if an error occurs. */
extern int input_error;




Header files are often used to document functions and globals
The variable input_error is declared here to exist and be of type int
We must still define it in some source file
The function input_double is declared to return double and expect as an
argument a string (trust me for now)
 This allows us to pass literals such as "Enter the radius" into the function so
that it can incorporate it into a prompt
5-44
Functions and Program Structure I
Getting input - centralizing common code (2)
/*
* File : main.c
* Author: Robert C. Carden IV
*/
#include <stdio.h>
#include "cylinder.h"
#include "input.h"
int main (void)
{
double rad, height, a, volume;
printf ("%s %s\n",
"This program computes the volume",
"of a cylinder and the area of its base.");
rad = input_double ("Enter radius of the base");
if (input_error) return input_error;
height = input_double ("Enter height of cylinder");
if (input_error) return input_error;
a = circle_area (rad);
volume = cylinder_volume (rad, height);
printf ("Radius %.2f, height %.2f: area=%.2f,volume=%.2f\n",
rad, height, a, volume);
return 0;
}
 We now change main.c to utilize this function
 We pass our prompt into input_double and trust it to incorporate it into a
message to the user
 We also check the global variable input_error for any fatal errors
 The only real fatal error here is end of file (Ctrl-Z in Windows)
5-45
Functions and Program Structure I
Getting input - centralizing common code (3)
/*
* File : input.c
* Author: Robert C. Carden IV
*/
#include <stdio.h>
#include “input.h”
int input_error = 0; /* once only initialization */
double input_double (const char *prompt)
{
/* space for input - trust me */
static char buf[BUFSIZ + 1];
input_error = 0;
for (;;)
{
int ret;
double val;
/* print the user supplied prompt */
printf ("%s: ", prompt);
/* read in an entire line of input */
/* fgets() returns its argument if successful */
if (fgets (buf, BUFSIZ, stdin) != buf)
{
input_error = 1;
printf ("End of file encountered!!!\n");
return 0;
/* get out of Dodge */
}
/* let sscanf() search the string */
ret = sscanf (buf, "%lf", &val);
if (ret == 1)
{
/* success */
return val;
}
/* input error */
printf ("Double expected, got '%s' instead\n", buf);
}
}
5-46
Functions and Program Structure I
Running the program with the new input routine
 Above we have a successful, though error prone run
 Below, we show that our program does indeed handle end of file
5-47
Functions and Program Structure I
Dissection of the input routine - Global variables
 In input.c we find the following:
/*
* File : input.c
* Author: Robert C. Carden IV
*/
#include <stdio.h>
int input_error = 0; /* once only initialization */




Notice where input_error is defined...it is defined outside of a function
Variables declared thus are said to have external linkage
Space is allocated for them when the program begins execution
They are initialized when the program begins execution




Any function can have access to this variable
In our example, input_double() initialized and set this value
Our main() function accessed it
The glue that ties it all together is in the header file input.h where we see:
/*
* This global variable is initialized to 0 and
* set to a nonzero value if an error occurs.
*/
extern int input_error;
 The header file declares the existence of input_error by defining it to be an
integer with the storage class extern
 A variable with storage class extern has memory storage permanently assigned
to it
5-48
Functions and Program Structure I
Dissection of the input routine - Static variables
double input_double (const char * prompt)
{
/* space for input - trust me */
static char buf[BUFSIZ + 1];
input_error = 0;
 The declaration char buf[BUFSIZ] declares buf to be an array of BUFSIZ
contiguous characters
 It is not important now to understand what an array is
 The variable buf will be used for reading in a line of input using the function
gets()
 Without the storage class modifier static, memory for buf (BUFSIZ bytes
worth) will be allocated when we enter the function input_double() and will be
deallocated when we exit input_double()
 Variables like these are said to be automatic
 The declaration
double input_double (const char * prompt)
{
char buf[BUFSIZ + 1];
is equivalent to
double input_double(const char * prompt)
{
auto char buf[BUFSIZ + 1];
 Contrast this to
double input_double(const char * prompt)
{
static char buf[BUFSIZ + 1];
 The storage class static makes this variable permanent
 Like global variables, space for it is allocated when the program begins
execution and deallocated when the program terminates
 The variable also retains it value between calls to input_double
5-49
Functions and Program Structure I
Dissection of the input routine - Block scope
for (;;)
{
int ret;
double val;
/* print the user supplied prompt */
printf ("%s: ", prompt);
/* read in an entire line of input */
/* fgets() returns its argument if successful */
if (fgets (buf, BUFSIZ, stdin) != buf )
{
input_error = 1;
printf ("End of file encountered!!!\n");
return 0;
/* get out of Dodge */
}
/* let sscanf() search the string */
ret = sscanf (buf, "%lf", &val);
if (ret == 1)
{
/* success */
return val;
}
/*input error*/
printf ("Double expected, got '%s' instead\n", buf);
}
} /*end of input_double() */
 In the code fragment above, we declared the auto variables ret and val inside
the compound statement that is associated with the for loop
 Compound statements with variable declarations are often called blocks
 Space for the auto variables is allocated each time we enter the block and
deallocated each time we leave the block
 Auto variables inside blocks are initialized each time we enter the block
5-50
Functions and Program Structure I
Dissection of the input routine - Block scope (2)
 To hammer home the second point about initialization, consider changing the
code fragment on the previous page thus:
for (;;)
{
int ret = 100;
double val = 3.14159;
printf ("** Initial ret=%d, val=%.2f\n", ret, val);
...
}
} /*end of input_double() */
 We can test to see if these variables are really truly initialized by intentionally
typing in erroneous input
 The function scanf() will return 0 as ret and if the initializations here did not
take place, we would see a value of 0 for ret in the printf() above
5-51
Functions and Program Structure I
Dissection of the input routine - Block scope (3)
 Now, consider changing the code fragment on the previous page thus:
for (;;)
{
static int ret = 100;
static double val = 3.14159;
printf ("** Initial ret=%d, val=%.2f\n", ret, val);
...
}
} /*end of input_double() */
 Here we change the storage class of these variables from auto to static
 The variables are initialized when the program begins execution and retain their
values between block invocations and calls to this function
5-52
Functions and Program Structure I
Dissection of the input routine - Final points
for (;;)
{
int ret; double val;
/* print the user supplied prompt */
printf("%s: ", prompt);
/* read in an entire line of input */
/* fgets() returns its argument if successful */
if (fgets (buf, BUFSIZ, stdin) != buf )
{
input_error = 1;
printf ("End of file encountered!!!\n");
return 0;
/* get out of Dodge */
}
/* let sscanf() search the string */
ret = sscanf (buf, "%lf", &val);
if (ret == 1)
{
/*success*/
return val;
}
/*input error*/
printf ("Double expected, got '%s' instead\n", buf);
}
 The function fgets() reads an entire line into the supplied buffer
 The only way out of the loop in this code fragment is to return
 Setting input_error to 1 is the only way we can tell the user we had an error on
input
 Function sscanf() is just like scanf() except that is requires one additional
parameter before the format
 Instead of reading input from the terminal (standard input), it reads it from the
user supplied string (buffer)
 Functions in the scanf() family return the number of inputs that they matched
5-53
Download