v3.0.2 / chapter 1 of 5 / 01 jul 07 / greg goebel / public domain
* The C programming language is sometimes referred to as a "middle-level" language. It provides low-level programming capability at the expense of some user-friendliness. Some cynics claim that "C combines the flexibility and power of assembly language with the user-friendliness of assembly language."
The original implementations of C were defined as described in the classic reference, THE C PROGRAMMING LANGUAGE, by Brian Kernighan and Dennis Ritchie. This definition left a few things to be desired, and the American National Standards Institute (ANSI) formed a group in the 1980s to tighten up the spec. The result was "ANSI C", which is the focus of this document.
* Here's a simple C program to calculate the volume of a sphere:
/* sphere.c */
#include <stdio.h> /* Include header file for printf. */
#define PI 3.141592654 /* Define a constant. */
float sphere( int rad ); /* Function prototype. */
void main() /* Main program. */
{
float vol; /* Declare variable. */
int radius = 3; /* Declare and initialize variable. */
vol = sphere( radius ); /* Call function, get result, print it. */
printf( "Volume: %f\n", vol );
}
float sphere( int rad ) /* Function. */
{
float result; /* Local variable declaration. */
result = rad * rad * rad;
result = 4 * PI * result / 3;
return( result ); /* Result returned to main program. */
}
The first feature that can be observed in this program is that comments are
enclosed by "/*" and "*/". Comments can go almost anywhere, since the
compiler ignores them. Another obvious feature is that most lines of
statements in the program end with a ";" and that some do not. Either
forgetting a ";" or adding one where it isn't needed is a common C
programming bug. Lines of code are grouped by using curly brackets ("{ }").
C is case-sensitive. All C keywords are in lower-case. Program variable and function names in whatever case desired, but by convention they should be in lower case. "Constants", to be discussed a bit later, are upper case by convention.
* Not all the lines in a C program are executable statements. Some of the statements shown above are "preprocessor directives".
C compilation is a multi-pass process. To create a program, a C compiler system performs the following steps:
These linking tasks are performed by a "linker", which takes one or more object files and links them to binary library files to create an "executable" file that can actually be run.
C has a large number of libraries and library functions. C by itself has few statements, so much of its functionality is implemented as library calls.
Commands intended for the C preprocessor, instead of the C compiler itself,
start with a "#" and are known as "preprocessor directives" or
"metacommands". The example program above has two such metacommands:
#include <stdio.h>
#define PI 3.14
The first statement, "#include <stdio.h>", simply merges the contents of the
file "stdio.h" into the current program file before compilation. The
"stdio.h" file contains declarations required for use of the standard-I/O
library, which provides the "printf()" function.
Incidentally, the "stdio.h" file, or "header file", only contains
declarations. The actual code for the library is contained in separate
library files that are added at link time. Custom header files loaded with
custom declarations can be created if needed, and then included in a program
as follows:
#include "mydefs.h"
Angle brackets ("< >") do not need to be used with custom header files. They
only are used to define the default header files provided standard with a C
compiler.
* A C program is built up of one or more functions. The program above contains two user-defined functions, "main()" and "sphere()", as well as the "printf()" library function.
The "main()" function is mandatory when writing a self-contained program. It defines the function that is automatically executed when the program is run. All other functions will be directly or indirectly called by "main()".
A C function is called simply by specifying its name, with any arguments
enclosed in following parentheses, with commas separating the arguments. In
the program above, the "printf()" function is called as follows:
printf( "Volume: %f\n", volume );
This invocation provides two arguments. The first -- "Volume: %f\n" --
supplies text and some formatting information. The second -- "volume" --
supplies a numeric value.
A function may or may not return a value. The "sphere()" function does, and
so is invoked as follows:
volume = sphere( radius );
A function uses the "return" keyword to return a value. In the case of
"sphere", it returns the volume of the sphere with the statement:
return( result );
* All variables in a C program must be "declared" by specifying their name
and type. The example program declares two variables for the "main" routine:
float volume;
int radius = 3;
-- and one in the "sphere" routine:
float result;
The declarations of "volume" and "result" specify a floating-point, or real,
variable. The declaration of "radius" specifies an integer variable. The
declaration allows variables to be initialized when declared if need be, in
this case declaring "radius" and assigning it a value of "3".
All three of these declarations define "local" variables. Local variables
exist only within the functions that declare them. Variables of the same
name could be declared in different functions without interference. It is
also possible to create "global" variables that can be shared by all
functions by declaring them outside the program's functions, and then using
the "extern" keyword inside the functions to declare access to it.
/* global.c */
#include <stdio.h>
void somefunc( void );
int globalvar;
void main()
{
extern int globalvar;
globalvar = 42;
somefunc();
printf( "%d\n", globalvar );
}
void somefunc( void )
{
extern int globalvar;
printf( "%d\n", globalvar );
globalvar = 13;
}
* Besides the variable declarations, the example programs above also feature
a function declaration, or "function prototype", that allows the C compiler
to perform checks on calls to the function in the program and flag an error
if they are not correct:
float sphere( int rad );
The function prototypes declare the type of value the function returns (the
type will be "void" if it does not return a value), and the arguments that
are to be provided with the function.
* Finally, the "printf()" library function provides text output capabilities
for the program. The "printf()" function can be used to print a simple
message as follows:
printf( "Hello, world!" );
-- displays the text:
Hello, world!
Remember that "printf()" doesn't automatically add a "newline" to allow
following "printf()"s to print on the next display line. For example:
printf( "Twas bryllig " );
printf( "and the slithy toves" );
-- prints the text:
Twas bryllig and the slithy toves
A newline character ("\n") must be added to force a newline. For example:
printf( "Hello,\nworld!" );
-- gives:
Hello,
world!
These examples only print a predefined text constant. It is possible to
include "format codes" in the string and then follow the string with one or
more variables to print the values they contain:
printf( " Result = %f\n", result );
This would print something like:
Result = 0.5
The "%f" is the format code that tells "printf" to print a floating-point
number. For another example:
printf( "%d times %d = %d\n", a, b, a * b );
-- would print something like:
4 * 10 = 40
The "%d" prints an integer quantity. Math or string expressions and
functions can be included in the argument list.
To simply print a string of text, there is a simpler function, "puts()", that
displays the specified text and automatically appends a newline:
puts( "Hello, world!" );
* Just for fun, let's take a look at what our example program would be like
in the pre-ANSI versions of C:
/* oldspher.c */
#include <stdio.h>
#define PI 3.141592654
float sphere(); /* Parameters not defined in function prototype. */
main()
{
float volume;
int radius = 3;
volume = sphere( radius );
printf( "Volume: %f\n", volume );
}
float sphere( rad )
int rad; /* Parameter type not specified in function header. */
{
float result;
result = rad * rad * rad;
result = 4 * PI * result / 3;
return result;
}
* The following sections elaborate on the principles outlined in this
section.
* As noted previously, any C program must have a "main()" function to contain the code executed by default when the program is run.
A program can contain as many functions as needed. All functions are
"visible" to all other functions. For example:
/* fdomain.c */
#include <stdio.h>
void func1( void );
void func2( void );
void main()
{
puts( "MAIN" );
func1();
func2();
}
void func1( void )
{
puts( "FUNC1" );
func2();
}
void func2( void )
{
puts( "FUNC2" );
func1();
}
In this example, "main()" can call "func1()" and "func2()"; "func1()" can
call "func2()"; and "func2()" can call "func1()". In principle, even
"main()" could be called by other functions, but it's hard to figure out why
anyone would want to do so. Although "main()" is the first function in the
listing above, there's no particular requirement that it be so, but by
convention it always should be.
Functions can call themselves recursively. For example, "func1()" can call "func1()" indefinitely, or at least until a stack overflow occurs. You cannot declare functions inside other functions.
Functions are defined as follows:
float sphere( int rad )
{
...
}
They begin with a function header that starts with a return value type
declaration ("float" in this case), then the function name ("sphere"), and
finally the arguments required ("int rad").
ANSI C dictates that function prototypes be provided to allow the compiler to
perform better checking on function calls:
float sphere( int rad );
For an example, consider a simple program that "fires" a weapon (simply by
printing "BANG!"):
/* bango.c */
#include <stdio.h>
void fire( void );
void main()
{
printf( "Firing!\n" );
fire();
printf( "Fired!\n" );
}
void fire( void )
{
printf( "BANG!\n" );
}
Since "fire()" does not return a value and does not accept any arguments,
both the return value and the argument are declared as "void"; "fire()" also
does not use a "return" statement and simply returns automatically when
completed.
Let's modify this example to allow "fire()" to accept an argument that
defines a number of shots. This gives the program:
/* fire.c */
#include <stdio.h>
void fire( int n );
void main()
{
printf( "Firing!\n" );
fire( 5 );
printf( "Fired!\n" );
}
void fire( int n )
{
int i;
for ( i = 1; i <= n ; ++i )
{
printf( "BANG!\n" );
}
}
This program passes a single parameter, an integer, to the "fire()" function.
The function uses a "for" loop to execute a "BANG!" the specified number of
times -- more on "for" later.
If a function requires multiple arguments, they can be separated by commas:
printf( "%d times %d = %d\n", a, b, a * b );
The word "parameter" is sometimes used in place of "argument". There is
actually a fine distinction between these two terms: the calling routine
specifies "arguments" to the called function, while the called function
receives the "parameters" from the calling routine.
When a parameter is listed in the function header, it becomes a local variable to that function. It is initialized to the value provided as an argument by the calling routine. If a variable is used as an argument, there is no need for it to have the same name as the parameter specified in the function header.
For example:
fire( shots );
...
void fire( int n )
...
The integer variable passed to "fire()" has the name "shots", but "fire()"
accepts the value of "shots" in a local variable named "n". The argument and
the parameter could also have the same name, but even then they would remain
distinct variables.
Parameters are, of course, matched with arguments in the order in which they
are sent:
/* pmmatch.c */
#include <stdio.h>
void showme( int a, int b );
void main()
{
int x = 1, y = 100;
showme( x, y );
}
void showme( int a, int b )
{
printf( "a=%d b=%d\n", a, b );
}
This prints:
a=1 b=100
This program can be modified to show that the arguments are not affected by
any operations the function performs on the parameters, as follows:
/* noside.c */
#include <stdio.h>
void showmore( int a, int b );
void main()
{
int x = 1, y = 100;
showmore( x, y );
printf( "x=%d y=%d\n", x, y );
}
void showmore( int a, int b )
{
printf( "a=%d b=%d\n", a, b );
a = 42;
b = 666;
printf( "a=%d b=%d\n", a, b );
}
This prints:
a=1 b=100
a=42 b=666
x=1 y=100
* Arrays can be sent to functions as if they were any other type of variable:
/* fnarray.c */
#include <stdio.h>
#define SIZE 10
void testfunc( int a[] );
void main()
{
int ctr, a[SIZE];
for( ctr = 0; ctr < SIZE; ++ctr )
{
a[ctr] = ctr * ctr;
}
testfunc( a );
}
void testfunc( int a[] )
{
int n;
for( n = 0; n < SIZE; ++ n )
{
printf( "%d\n", a[n] );
}
}
It is possible to define functions with a variable number of parameters. In
fact, "printf()" is such a function. This is a somewhat advanced issue and
we won't worry about it further in this document.
* The normal way to get a value out of a function is simply to provide it as
a return value. This neatly encapsulates the function and isolates it from
the calling routine. In the example in the first section, the function
"sphere()" returned a "float" value with the statement:
return( result );
The calling routine accepted the return value as follows:
volume = sphere( radius );
The return value can be used directly as a parameter to other functions:
printf( "Volume: %f\n", sphere( radius ) );
The return value does not have to be used; "printf()", for example, returns
the number of characters it prints, but few programs bother to check.
A function can contain more than one "return" statement::
if( error == 0 )
{
return( 0 );
}
else
{
return( 1 );
}
A "return" can be placed anywhere in a function. It doesn't have to return a
value; without a value, "return" simply causes an exit from the function.
However, this does imply that the data type of the function must be declared
as "void":
void ftest( int somevar )
{
...
if( error == 0 )
{
return();
}
...
}
If there's no "return" in a function, the function returns after it executes
its last statement. Again, this means the function type must be declared
"void".
* The "return" statement can only return a single value, but this value can be a "pointer" to an array or a data structure. Pointers are a complicated subject and will be discussed in detail later.
* C contains a number of looping constructs, such as the "while" loop:
/* while.c */
#include <stdio.h>
void main()
{
int test = 10;
while( test > 0 )
{
printf( "test = %d\n", test );
test = test - 2;
}
}
If "test" starts with an initial value less than or equal to 0 the "while"
loop will not execute even once. There is a variant, "do", that will always
execute at least once:
/* do.c */
#include <stdio.h>
void main()
{
int test = 10;
do
{
printf( "test = %d\n", test );
test = test - 2;
}
while( test > 0 );
}
The most common looping construct is the "for" loop, which creates a loop
much like the "while" loop but in a more compact form:
/* for.c */
#include <stdio.h>
void main()
{
int test;
for( test = 10; test > 0; test = test - 2 )
{
printf( "test = %d\n", test );
}
}
Notice that with all these loops, the initial loop statement does not end
with a ";". If a ";" was placed at the end of the "for" statement above, the
"for" statement would execute to completion, but not run any of the
statements in the body of the loop.
The "for" loop has the syntax:
for( <initialization>; <operating test>; <modifying expression> )
All the elements in parentheses are optional. A "for" loop could be run
indefinitely with:
for( ; ; )
{
...
}
-- although using an indefinite "while" is cleaner:
while( 1 )
{
...
}
It is possible to use multiple expressions in either the initialization or
the modifying expression with the "," operator:
/* formax.c */
#include <stdio.h>
void main()
{
int a, b;
for ( a = 256, b = 1; b < 512 ; a = a / 2, b = b * 2 )
{
printf( "a = %d b = %d\n", a, b );
}
}
The conditional tests available to C are as follows:
a == b equals
a != b not equals
a < b less than
a > b greater than
a <= b less than or equals
a >= b greater than or equals
The fact that "==" is used to perform the "equals" test, while "=" is used as
the assignment operator, often causes confusion and is a common bug in C
programming:
a == b Is "a" equal to "b"?
a = b Assign value of "b" to "a".
* C also contains decision-making statements, such as "if":
/* if.c */
#include <stdio.h>
#define MISSILE 1
void fire( int weapon )
void main()
{
fire( MISSILE );
}
void fire( int weapon )
{
if( weapon == MISSILE )
{
printf( "Fired missile!\n" );
}
if( weapon != MISSILE )
{
printf( "Unknown weapon!\n");
}
}
This example can be more easily implemented using an "else" clause:
/* ifelse.c */
void fire( int weapon )
{
if( weapon == MISSILE )
{
printf( "Fired missile!\n" );
}
else
{
printf( "Unknown weapon!\n");
}
}
Since there is only one statement in each clause the curly brackets aren't
really necessary. This example would work just as well:
void fire( int weapon )
{
if( weapon == MISSILE )
printf( "Fired missile!\n" );
else
printf( "Unknown weapon!\n" );
}
However, the brackets make the structure more obvious and prevent errors if
you add statements to the conditional clauses. The compiler doesn't care one
way or another, it generates the same code.
There is no "elseif" keyword, but "if" statements can be nested:
/* nestif.c */
#include <stdio.h>
#define MISSILE 1
#define LASER 2
void fire( int weapon )
void main()
{
fire( LASER );
}
void fire( int weapon )
{
if( weapon == MISSILE )
{
printf( "Fired missile!\n" );
}
else
{
if( weapon == LASER )
{
printf( "Fired laser!\n" );
}
else
{
printf( "Unknown weapon!\n");
}
}
}
This is somewhat clumsy. The "switch" statement does a cleaner job:
/* switch.c */
void fire( int weapon )
{
switch( weapon )
{
case MISSILE:
printf( "Fired missile!\n" );
break;
case LASER:
printf( "Fired laser!\n" );
break;
default:
printf( "Unknown weapon!\n");
break;
}
}
The "switch" statement tests the value of a single variable; unlike the "if"
statement, it can't test multiple variables. The optional "default" clause
is used to handle conditions not covered by the other cases.
Each clause ends in a "break", which causes execution to break out of the
"switch". Leaving out a "break" can be another subtle error in a C program,
since if it isn't there, execution flows right through to the next clause.
However, this can be used to advantage. Suppose in our example the routine
can also be asked to fire a ROCKET, which is the same as a MISSILE:
void fire( int weapon )
{
switch( weapon )
{
case ROCKET:
case MISSILE:
printf( "Fired missile!\n" );
break;
case LASER:
printf( "Fired laser!\n" );
break;
default:
printf( "Unknown weapon!\n");
break;
}
}
The "break" statement is not specific to "switch" statements. It can be used
to break out of other control structures, though good program design tends to
avoid such improvisations:
/* break.c */
#include <stdio.h>
void main()
{
int n;
for( n = 0; n < 10; n = n + 1 )
{
if( n == 5 )
{
break; /* Punch out of loop at value 5. */
}
else
{
printf( "%d\n", n );
}
}
}
If the "for" loop were nested inside a "while" loop, a "break" out of the
"for" loop would still leave you stuck in the "while" loop. The "break"
keyword only applies to the control construct that executes it.
There is also a "continue" statement that skips to the end of the loop body
and continues with the next iteration of the loop. For example:
/* continue.c */
#include <stdio.h>
void main()
{
int n;
for( n = 0; n < 10; n = n + 1 )
{
if( n == 5 )
{
continue;
}
else
{
printf( "%d\n", n );
}
}
}
Finally, there is a "goto" statement:
goto punchout;
...
punchout:
-- that jumps to an arbitrary tag within a function, but the use of this
statement is generally discouraged and it is rarely seen in practice.
* While these are the lot of C's true control structures, there is also a
special "conditional operator" that performs a simple conditional assignment
of the form:
if( a == 5)
{
b = -10;
}
else
{
b = 255;
}
-- using a much tidier, if more cryptic, format:
b = ( a == 5 ) ? -10 : 255 ;
BACK_TO_TOP