| Add comments here | |
|
|
| |
The C programming language was invented for the purposes
of writing an operating system that could be recompiled (ported)
to different hardware platforms (different CPU's). It is hence also
the first choice for writing any kind of application that has to
communicate efficiently with the operating system.
|
| |
Many people who don't program in C very well think of C as
an arbitrary language out of many. This point should be made at
once: C is the fundamental basis of all computing in the world
today. UNIX, Microsoft Windows, office suites, web browsers
and device drivers are all written in C. 99% of your time spent
at a computer is probably spent inside a C
application25.1.
|
| |
There is also no replacement for C. Since it fulfils its purpose
without much flaw, there will never be a need to replace it.
Other languages may fulfil other purposes, but C fulfils
its purpose most adequately. For instance, all future operating
systems will probably be written in C for a long time to come.
|
| |
It is for these reasons that your knowledge of UNIX will never be
complete until you can program in C.
|
| |
|
| |
|
| |
A simple C program is:
5
|
#include <stdlib.h>
#include <stdio.h>
int main (int argc, char *argv[])
{
printf ("Hello World!\n");
return 3;
}
|
|
| |
Save this program in a file hello.c. You can
compile25.2 this
program with the command
|
gcc -Wall -o hello hello.c
|
the -o hello option tells
gcc25.3 to produce the binary file hello instead of the
default binary file name
a.out25.4.
The -Wall option means to report all Warnings
during the compilation. This is not strictly necessary but most helpful
for correcting possible errors in your programs.
|
| |
Then run the program with
|
| |
Previously you should have familiarised yourself with
bash functions (See Section 10.6).
In C all code is inside a function. The first function to
be called (by the operating system) in the main function.
|
| |
Type echo $? to see the return code of the program. You will
see it is 3, indicating the return value of the main
function.
|
| |
Other things to note are the " on either side of the string
to be printed. Quotes are required around string literals. Inside
a string literal \n escape sequence
indicates a newline character. ascii7 shows some
other escape sequences. You can also see a proliferance of
; everywhere in a C program. Every statement in C is
separated by a ; unlike with shell scripts where a ;
is optional.
|
| |
Now try:
5
|
#include <stdlib.h>
#include <stdio.h>
int main (int argc, char *argv[])
{
printf ("number %d, number %d\n", 1 + 2, 10);
exit (3);
}
|
printf may be thought of as the command to send output
to the terminal. It is also what is known as a standard C
library function. In other words, it is specified that a C
implementation should always have the printf function
and that it should behave in a certain way.
|
| |
The %d indicates that a decimal should
go in at that point in the text. The number to be substituted
will be the first argument to the printf function
after the string literal -- i.e. the 1 + 2. The
next %d is substituted with the second argument
-- i.e. the 10. The %d is known as a format
specifier. It essentially converts an integer number into
a decimal representation.
|
| |
|
| |
With bash you could use a variable anywhere anytime, and the
variable would just be blank if it was never used before. In C
you have to tell the compiler what variables you are going to need
before each block of code.
|
| |
This is done with a variable declaration:
5
10
|
#include <stdlib.h>
#include <stdio.h>
int main (int argc, char *argv[])
{
int x;
int y;
x = 10;
y = 2:
printf ("number %d, number %d\n", 1 + y, x);
exit (3);
}
|
The int x is a variable declaration. It tells the
program to reserve space for one integer variable
and that it will later be referred to as x. int is the
type of the variable. x = 10 assigned the variable
with the value 10. There are types for each of the kind of numbers
you would like to work with, and format specifiers to convert them
for printing:
5
10
15
20
|
#include <stdlib.h>
#include <stdio.h>
int main (int argc, char *argv[])
{
char a;
short b;
int c;
long d;
float e;
double f;
long double g;
a = 'A';
b = 10;
c = 10000000;
d = 10000000;
e = 3.14159;
f = 10e300;
g = 10e300;
printf ("%c, %hd, %d, %ld, %f, %f, %Lf\n", a, b, c, d, e, f, g);
exit (3);
}
|
|
| |
You will notice that %f is used for both floats and
doubles. This is because floats are always converted
to doubless during an operating like this. Also try replacing
%f with %e to print in exponential notation -- i.e. less significant
digits.
|
| |
|
| |
Functions are implemented as follows:
5
10
|
#include <stdlib.h>
#include <stdio.h>
void mutiply_and_print (int x, int y)
{
printf ("%d * %d = %d\n", x, y, x * y);
}
int main (int argc, char *argv[])
{
mutiply_and_print (30, 5);
mutiply_and_print (12, 3);
exit (3);
}
|
|
| |
Here we have a non-main function called by the
main function. The function is first declared
with
|
void mutiply_and_print (int x, int y)
|
This declaration states the return value of the function
(void for no return value); the function name
(mutiply_and_print) and then the arguments that
are going to be passed to the function. The numbers passed to
the function are given their own names, x and y,
and are converted to the type of x and y before
being passed to the function -- in this case, int and
int. The actual C code of which the function is
comprised goes between curly braces { and }.
|
| |
In other word, the above function is the same as:
5
|
void mutiply_and_print ()
{
int x;
int y;
x = <first-number-passed>
y = <second-number-passed>
printf ("%d * %d = %d\n", x, y, x * y);
}
|
(Note that this is not permissable C code.)
|
| |
|
| |
As with shell scripting, we have the for, while and
if statements:
5
10
15
20
25
30
35
40
45
|
#include <stdlib.h>
#include <stdio.h>
int main (int argc, char *argv[])
{
int x;
x = 10;
if (x == 10) {
printf ("x is exactly 10\n");
x++;
} else if (x == 20) {
printf ("x is not equal to 20\n");
} else {
printf ("No, x is not equal to 10 or 20\n");
}
if (x > 10) {
printf ("Yes, x is more than 10\n");
}
while (x > 0) {
printf ("x is %d\n", x);
x = x - 1;
}
for (x = 0; x < 10; x++) {
printf ("x is %d\n", x);
}
switch (x) {
case 9:
printf ("x is nine\n");
break;
case 10:
printf ("x is ten\n");
break;
case 11:
printf ("x is eleven\n");
break;
default:
printf ("x is huh?\n");
break;
}
return 0;
}
|
It is easy to see the format that these take, although they
are vastly different from shell scripts. C code works in
statement blocks between curly braces, in the same way
that shell scripts have do's and done's.
|
| |
Note that with most programming languages when we want to
add 1 to a variable we have to write, say x = x + 1.
In C the abbreviation x++ is used, meaning to
increment a variable by 1.
|
| |
The for loop takes three statements between (
...). These are, a statement to start things off, a comparison, and
a statement to be executed everytime after the statement block.
The statement block after the for is executed until
the comparison is untrue.
|
| |
The switch statement is like case in shell scripts.
switch considers the argument inside its ( ...
) and decides which case line to jump to. In this
case it will obviously be printf ("x is ten\n"); because
x was 10 when the previous for loop exited.
The break tokens means we are done with the switch
statement and that execution should continue from Line 46.
|
| |
Note that in C the comparison == is used instead of =.
= means to assign a value to a variable, while ==
is an equality operator.
|
| |
|
| |
You can define a list of numbers with:
This is called an array:
5
10
15
|
#include <stdlib.h>
#include <stdio.h>
int main (int argc, char *argv[])
{
int x;
int y[10];
for (x = 0; x < 10; x++) {
y[x] = x * 2;
}
for (x = 0; x < 10; x++) {
printf ("item %d is %d\n", x, y[x]);
}
return 0;
}
|
|
| |
If an array is of type character then it is called a
string:
5
10
15
|
#include <stdlib.h>
#include <stdio.h>
int main (int argc, char *argv[])
{
int x;
char y[11];
for (x = 0; x < 10; x++) {
y[x] = 65 + x * 2;
}
for (x = 0; x < 10; x++) {
printf ("item %d is %d\n", x, y[x]);
}
y[10] = 0;
printf ("string is %s\n", y);
return 0;
}
|
Note that a string has to be null-terminated. This means
that the last character must be a zero. The code y[10] = 0
sets the eleventh item in the array to zero. This also means that
strings need to be one char longer than you would think.
|
| |
(Note that the first item in the array is y[0], not y[1],
like some other programming languages.)
|
| |
In the above example, the line char y[11] reserved 11
bytes for the string. Now what if you want a string of 100000
bytes? C allows you to allocate memory for your 100k
which means requesting memory from the kernel. Any non-trivial
program will allocate memory for itself and there is no other
way of getting getting large blocks of memory for your program
to use. Try:
5
10
15
|
#include <stdlib.h>
#include <stdio.h>
int main (int argc, char *argv[])
{
int x;
char *y;
y = malloc (11);
printf ("%ld\n", y);
for (x = 0; x < 10; x++) {
y[x] = 65 + x * 2;
}
y[10] = 0;
printf ("string is %s\n", y);
free (y);
return 0;
}
|
The declaration char *y would be new to you. It means to
declare a variable (a number) called y that points
to a memory location. The * (asterix) in this context
means pointer. Now if you have a machine with perhaps 256
megabytes of RAM + swap, then y will have a range of
about this much. The numerical value of y is also printed
with printf ("%ld\n", y);, but is of no
interest to the programmer.
|
| |
When finished using memory it should be given back to the operating
system. This is done with free. Programs that don't free
all the memory they allocate are said to leak memory.
|
| |
Allocating memory often requires you to perform a calculation to
determine the amount of memory required. In the above case we
are allocating the space of 11 char's. Since each
char is really a single byte, this presents no problem. But
what if we were allocating 11 int's? An int on a
PC is 32 bits -- four bytes. To determine the size of a type,
we use the sizeof keyword:
5
10
15
20
|
#include <stdlib.h>
#include <stdio.h>
int main (int argc, char *argv[])
{
int a;
int b;
int c;
int d;
int e;
int f;
int g;
a = sizeof (char);
b = sizeof (short);
c = sizeof (int);
d = sizeof (long);
e = sizeof (float);
f = sizeof (double);
g = sizeof (long double);
printf ("%d, %d, %d, %d, %d, %d, %d\n", a, b, c, d, e, f, g);
return 0;
}
|
Here you can see the number of bytes required by all of these types.
Now we can easily allocate arrays of things other than char.
5
10
15
|
#include <stdlib.h>
#include <stdio.h>
int main (int argc, char *argv[])
{
int x;
int *y;
y = malloc (10 * sizeof (int));
printf ("%ld\n", y);
for (x = 0; x < 10; x++) {
y[x] = 65 + x * 2;
}
for (x = 0; x < 10; x++) {
printf ("%d\n", y[x]);
}
free (y);
return 0;
}
|
On many machines an int is four bytes (32 bits), but you
should never assume this. Always use the sizeof
keyword to allocate memory.
|
| |
|
| |
C programs probably do more string manipulation than
anything else. Here is a program that divides a sentence
up into words:
5
10
15
20
25
30
|
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main (int argc, char *argv[])
{
int length_of_word;
int i;
int length_of_sentace;
char p[256];
char *q;
strcpy (p, "hello there, my name is fred.");
length_of_sentace = strlen (p);
length_of_word = 0;
for (i = 0; i <= length_of_sentace; i++) {
if (p[i] == ' ' || i == length_of_sentace) {
q = malloc (length_of_word + 1);
strncpy (q, p + i - length_of_word, length_of_word);
q[length_of_word] = 0;
printf ("word: %s\n", q);
free (q);
length_of_word = 0;
} else {
length_of_word = length_of_word + 1;
}
}
return 0;
}
|
Here we introduce three more standard
C library functions. strcpy stands for
stringcopy. It copies
memory from one place to another. Line 13 of this program
copies text into the character array p, which
is called the target of the copy.
|
| |
strlen stands for stringlength.
It determines the length of a string, which is just a count of
the number of characters up to the null character.
|
| |
We need to loop over the length of the sentence. The variable
i indicates the current position in the sentence.
|
| |
Line 20 says that if
we find a character 32 (denoted by ' ') we know we have
reached a word boundary. We also know that the end of the
sentence is a word boundary even though there may not be a
space there. The token || means OR. At
this point we can allocate memory for the
current word, and copy the word into that memory. The
strncpy function is useful for this. It copies
a string, but only up to a limit of length_of_word
characters (the last argument). Like strcpy, the first
argument is the target, and the second argument is the
place to copy from.
|
| |
To calculate the position of the start of the last word, we use
p + i - length_of_word. This means that we are adding
i to the memory location p and then going back
length_of_word counts thus pointing strncpy to the
exact position.
|
| |
Finally, we null terminate the string on Line 23. We can then print
q, free the used memory, and begin with the next
word.
|
| |
To get a complete list of string operations, see string3.
|
| |
|
| |
Under most programming languages, file operations involve
three steps: opening a file, reading or
writing to the file, and then closing the
file. The command fopen is commonly uses to
tell the operating system that you are ready to begin
working with a file:
|
| |
The following program opens a file and spits it
out on the terminal:
5
10
15
|
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main (int argc, char *argv[])
{
int c;
FILE *f;
f = fopen ("test.c", "r");
for (;;) {
c = fgetc (f);
if (c == -1)
break;
printf ("%c", c);
}
fclose (f);
return 0;
}
|
A new type is presented here: FILE *. It is a file
operations variable that has to be initialised with
fopen before we can use it. The fopen
function takes two arguments: the first is the name of the file and
the second is string explaining how we want to open the
file -- in this case "r" means reading
from the start of the file. Other options are "w" for
writing and several more described in fopen3.
|
| |
The command fgetc gets a character from the file.
It retrieves consecutive bytes from the file until it reaches the
end of the file, where it returns a -1. The break
statement indicates to immediately terminate the for loop,
whereupon execution will continue from Line 17. break
statements can appear inside while loops as well.
|
| |
You will notice that the for loop is empty. This is
allowable C code and means to loop forever.
|
| |
Some other file functions are fread, fwrite,
fputc, fprintf and fseek. See fwrite3,
fputc3, fprintf3 and fseek3.
|
| |
|
| |
Up until now, you are probably wondering what the
(int argc, char *argv[]) are for. These are
the command-line arguments passed to the program by
the shell. argc is the number of command-line
arguments and argv is an array of strings of
each argument. Printing them out is easy:
|
| |
5
10
|
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main (int argc, char *argv[])
{
int i;
for (i = 0; i < argc; i++) {
printf ("argument %d is %s\n", i, argv[i]);
}
return 0;
}
|
|
| |
|
| |
Here we put this altogether in a program that reads in lots of files and
dumps them as words. Some new things in the following program
are: != is the inverse of ==. It tests if
not-equal-to; realloc reallocates
memory -- it resizes an old block of memory so that any
bytes of the old block are preserved; \n,
\t mean the newline character, 10, or the
tab character, 9, respectively.
5
10
15
20
25
30
35
40
45
50
55
60
|
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void word_dump (char *filename)
{
int length_of_word;
int amount_allocated;
char *q;
FILE *f;
int c;
c = 0;
f = fopen (filename, "r");
length_of_word = 0;
amount_allocated = 256;
q = malloc (amount_allocated);
while (c != -1) {
if (length_of_word >= amount_allocated) {
amount_allocated = amount_allocated * 2;
q = realloc (q, amount_allocated);
}
c = fgetc (f);
q[length_of_word] = c;
if (c == -1 || c == ' ' || c == '\n' || c == '\t') {
if (length_of_word > 0) {
q[length_of_word] = 0;
printf ("%s\n", q);
}
amount_allocated = 256;
q = realloc (q, amount_allocated);
length_of_word = 0;
} else {
length_of_word = length_of_word + 1;
}
}
fclose (f);
}
int main (int argc, char *argv[])
{
int i;
if (argc < 2) {
printf ("Usage:\n\twordsplit <filename> ...\n");
exit (1);
}
for (i = 1; i < argc; i++) {
word_dump (argv[i]);
}
return 0;
}
|
|
| |
This program is more complicated than you might immediately
expect. Reading in a file where we know that a word will
never exceed 30 characters is simple. But what if we have a file
that contains some words that are 100000 characters long? GNU
programs are expected to behave correctly under these
circumstances.
|
| |
To cope with normal as well as extreme circumstances, we
assume to start off with that a word will never be more than 256
characters. If it appears that the word is growing passed 256
characters, we reallocate the memory space to
double its size (Line 24 amd 25). When we start with a new word, we can free up
memory again, so we realloc back to 256 again (Line 36 and 37). In this
way are only use the minimum amount of memory at each
point in time.
|
| |
We have hence created a program that can work efficiently with a
100 Gigabyte file just as easily as with a 100 byte file. This
is part of the art of C programming.
|
| |
Experiences C programmers may actually scoff at the above
listing because it really isn't as ``minimalistic'' as you may be
able to write it with more experience. In fact it is really a truly
excellent listing for the simple reason that, firstly, it is easy to
understand, and secondly, it is an efficient algorithm (albeit not
optimal). Readability in C is your first priority -- it is
imperative that what you do is obvious to anyone
reading the code.
|
| |
|
| |
At the start of each program will be one or more #include
statements. These tell the compiler to read in another C program.
Now ``raw'' C does not have a whole lot in the way of protecting
against errors: for example the strcpy function could
just as well be used with one, three or four arguments, and the
C program would still compile. It would however reek havoc
with the internal memory and cause the program to crash. These
other .h C programs are called header files that
contain templates for how functions are meant to be called.
Every function you might like to use is contained in one or other
template file. The templates are called function
prototypes.
|
| |
A function prototype is written the same as the function itself,
but without the code. A function prototype for word_dump
would simply be:
|
void word_dump (char *filename);
|
The trailing ; is essential and distinguishes a function
from a function prototype.
|
| |
After a function prototype, any attempt to use the function in a
way other than intended -- say, passing to few arguments or
arguments of the wrong type -- will be met with fierce
opposition from gcc.
|
| |
You will notice that the #include <string.h> appeared
when we started using string operations. Recompiling
these programs without the #include <string.h> line
give the warning message:
|
test.c:21: warning: implicit declaration of function `strncpy'
|
Which is quite to the point.
|
| |
The function prototypes give a clear definition of how every
function is to be used. man pages will always first state
the function prototype so that you are clear on what arguments
are to be passed, and what types they should have.
|
| |
|
| |
A C comment is denoted with /* <comment lines> */.
Anything between the /* and */ is ignored and can
span multiple lines. Every function
should be commented, and all non-obvious code should be
commented. It is a good rule that a program that needs
lots of comments to explain it is badly written. Also, never
comment the obvious and explain why you do things rather
that what you are doing. It is advisable not to
make pretty graphics between each function, so rather:
|
/* returns -1 on error, takes a positive integer */
int sqr (int x)
{
<...>
|
than
5
|
/***************************----SQR----******************************
* x = argument to make the square of *
* return value = *
* -1 (on error) *
* square of x (on success) *
********************************************************************/
int sqr (int x)
{
<...>
|
which is liable to give people nausea. Under C++, the additional comment
// is allowed, which ignores everything between the
// and the end of the line. It is accepted under gcc,
but should not be used unless you really are programming in C++. In addition,
programmers often ``comment out'' lines by placing an #if 0 ...
#endif around them, which really does exactly the same
thing as a comment (see Section 25.1.12), but allows
you to comment out comments as well eg:
5
|
int x;
x = 10;
#if 0
printf ("debug: x is %d\n", x); /* print debug information */
#endif
y = x + 10;
<...>
|
comments out Line 4.
|
| |
|
| |
|
| |
Anything starting with a # is not actually C, but a
C preprocessor directive. A C program is first
run through a preprocessor which removes all spurious
junk, like comments and #include statements. C programs
can be made much more readable by defining macros instead
of literal values. For instance:
|
#define START_BUFFER_SIZE 256
|
in our example program #defines the text
START_BUFFER_SIZE to be the text 256.
Thereafter wherever in the C program we have a
START_BUFFER_SIZE, the text 256 will be seen by
the compiler, and we can use START_BUFFER_SIZE
instead. This is a much cleaner way of programming,
because, if say we would like to change the 256 to some
other value, we only need to change it in one place.
START_BUFFER_SIZE is also more meaningful than a
number, making the program more readable.
|
| |
Whenever you have a literal constant like 256,
you should replace it with a macro defined near the top of
your program.
|
| |
You can also check for the existence of macros with the
#ifdef and #ifndef directive. # directives are
really a programming language all on their own:
5
10
15
20
25
|
/* Set START_BUFFER_SIZE to fine tune performance before compiling: */
#define START_BUFFER_SIZE 256
/* #define START_BUFFER_SIZE 128 */
/* #define START_BUFFER_SIZE 1024 */
/* #define START_BUFFER_SIZE 16384 */
#ifndef START_BUFFER_SIZE
#error This code did not define START_BUFFER_SIZE. Please edit
#endif
#if START_BUFFER_SIZE <= 0
#error Wooow! START_BUFFER_SIZE must be greater than zero
#endif
#if START_BUFFER_SIZE < 16
#warning START_BUFFER_SIZE to small, program may be inefficient
#elif START_BUFFER_SIZE > 65536
#warning START_BUFFER_SIZE to large, program may be inefficient
#else
/* START_BUFFER_SIZE is ok, do not report */
#endif
void word_dump (char *filename)
{
<...>
amount_allocated = START_BUFFER_SIZE;
q = malloc (amount_allocated);
<...>
|
|
| |
|
| |
We made reference to the Standard C Library. The C
language on its own does almost nothing; everything useful
is an external function. External functions are grouped
into libraries. The Standard C Library is the file
/lib/libc.so.6. To list all the C library functions, do:
|
nm /lib/libc.so.6
nm /lib/libc.so.6 | grep ' T ' | cut -f3 -d' ' | grep -v '^_' | sort -u | less
|
many of these have man pages, however some will have
no documentation and require you to read the comments inside
the header files. It is better not to use functions unless
you are sure that they are standard functions in the
sense that they are common to other systems.
|
| |
To create your own library is simple. Lets say we have two
files that contain functions that we would like to create
a library out of, simple_math_sqrt.c,
5
10
15
20
|
#include <stdlib.h>
#include <stdio.h>
static int abs_error (int a, int b)
{
if (a > b)
return a - b;
return b - a;
}
int simple_math_isqrt (int x)
{
int result;
if (x < 0) {
fprintf (stderr, "simple_math_sqrt: taking the sqrt of a negative number\n");
abort ();
}
result = 2;
while (abs_error (result * result, x) > 1) {
result = (x / result + result) / 2;
}
return result;
}
|
and, simple_math_pow.c
5
10
15
20
|
#include <stdlib.h>
#include <stdio.h>
int simple_math_ipow (int x, int y)
{
int result;
if (x == 1 || y == 0)
return 1;
if (x == 0 && y < 0) {
fprintf (stderr, "simple_math_pow: raising zero to a negative power\n");
abort ();
}
if (y < 0)
return 0;
result = 1;
while (y > 0) {
result = result * x;
y = y - 1;
}
return result;
}
|
|
| |
We would like to call the library simple_math.
It is good practice to name all the functions in the library
simple_math_??????. The function abs_error
is not going to be used outside of the file simple_math_sqrt.c
and hence has the keyword static in front of it, meaning
that it is a local function.
|
| |
We can compile the code with:
|
gcc -Wall -c simple_math_sqrt.c
gcc -Wall -c simple_math_pow.c
|
The -c option means the compile only. The
code is not turned into an executable. The generated
files are simple_math_sqrt.o and simple_math_pow.o.
These are called object files.
|
| |
We now need to archive these files into
a library. We do this with the ar command (a predecessor
to tar):
|
ar libsimple_math.a simple_math_sqrt.o simple_math_pow.o
ranlib libsimple_math.a
|
The ranlib command indexes the archive.
|
| |
The library can now be used. Create a file test.c:
5
|
#include <stdlib.h>
#include <stdio.h>
int main (int argc, char *argv[])
{
printf ("%d\n", simple_math_ipow (4, 3));
printf ("%d\n", simple_math_isqrt (50));
return 0;
}
|
and run:
|
gcc -Wall -c test.c
gcc -o test test.o -L. -lsimple_math
|
The first command compiles the file test.c into
test.o, while the second function is called
linking the program, which assimilates test.o
and the libraries into a single executable. The option
L. means to look in the current directory
for any libraries (usually only /lib and /usr/lib are
searched). The option -lsimple_math means to assimilate the
library libsimple_math.a (lib and .a are added
automatically). This operation is called static25.5 linking because it happens
before the program is run, and includes all object files into the
executable.
|
| |
We can also create a header file simple_math.h for using the
library.
5
|
/* calculates the integer square root, aborts on error */
int simple_math_isqrt (int x);
/* calculates the integer power, aborts on error */
int simple_math_ipow (int x, int y);
|
Add the line #include "simple_math.h" to
the top of test.c:
|
#include <stdlib.h>
#include <stdio.h>
#include "simple_math.h"
|
This will get rid of
the implicit declaration of function warning messages.
Usually #include <simple_math.h> would be used,
but here this is a header file in the current directory
-- our own header file -- and this is where
we use "simple_math.h" instead of <simple_math.h>.
|
| |
|
| |
Now what if you make a small change to one of the files
(as you are likely to do very often when developing)?
You could script the process of compiling and linking,
but the script would build everything, and not just the
changed file. What we really need is a utility that
only recompiles object files whose sources have changed:
make is such a utility.
|
| |
make is a program that looks inside a Makefile
in the current directory then does a lot of compiling and linking.
Makefiles contain lists of rules and dependencies
describing how to build a program.
|
| |
Inside a Makefile you need to state a list of
what-depends-on-what dependencies that make
can work through, as well as the shell commands needed to
achieve each goal.
|
| |
Our first (last?) dependency in the process of completing
the compilation is that test depends-on
both the library, libsimple_math.a, and
the object file, test.o. In make terms
we create a Makefile line that looks like:
|
test: libsimple_math.a test.o
|
meaning simply that the files libsimple_math.a test.o
must exist and be updated before test.
test: is called a make target.
Beneath this line, we also need to state
how to build test:
|
gcc -Wall -o $@ test.o -L. -lsimple_math
|
The $@ means the name of the target
itself which is just substituted with test. Note that the
space before the gcc is a tab character and not
8 space characters.
|
| |
The next dependency is that libsimple_math.a
depends on simple_math_sqrt.o simple_math_pow.o.
Once again we have a dependency, along with a shell
script to build the target. The full Makefile
rule is:
|
libsimple_math.a: simple_math_sqrt.o simple_math_pow.o
rm -f $@
ar rc $@ simple_math_sqrt.o simple_math_pow.o
ranlib $@
|
Note again that the left margin consists of a single tab character and not spaces.
|
| |
The final dependency is that the files simple_math_sqrt.o and simple_math_pow.o
depend on the files simple_math_sqrt.c and simple_math_pow.c.
The requires two make target rules, but make has a short
way of stating such a rule for where there are many C source files,
|
.c.o:
gcc -Wall -c -o $*.o $<
|
which means that any .o files needed
can be built from a .c file of a similar name using
the command gcc -Wall -c -o $*.o $<. $*.o
means the name of the object file and $< means
the name of the file that $*.o depends on, one at
a time.
|
| |
|
| |
Makefiles can in fact have their rules
put in any order, so its best to state the most obvious
rules first for readability.
|
| |
There is also a rule you should always state at the
outset:
|
all: libsimple_math.a test
|
The all: target is the rule that
make tries to satisfy when make is
run with no command-line arguments. This just
means that libsimple_math.a and test
are the last two files to be built, i.e. the top-level
dependencies.
|
| |
Makefiles also have their own form of environment
variables, like shell scripts. You can see that we have
used the text simple_math in three of our rules.
It makes sense to define a macro for this so that
if we can easily change to a different library name.
|
| |
Our final Makefile is:
5
10
15
20
|
# Comments start with a # (hash) character like shell scripts.
# Makefile to build libsimple_math.a and test program.
# Paul Sheer <psheer@cranzgots.co.za> Sun Mar 19 15:56:08 2000
OBJS = simple_math_sqrt.o simple_math_pow.o
LIBNAME = simple_math
CFLAGS = -Wall
all: lib$(LIBNAME).a test
test: lib$(LIBNAME).a test.o
gcc $(CFLAGS) -o $@ test.o -L. -l${LIBNAME}
lib$(LIBNAME).a: $(OBJS)
rm -f $@
ar rc $@ $(OBJS)
ranlib $@
.c.o:
gcc $(CFLAGS) -c -o $*.o $<
clean:
rm -f *.o *.a test
|
|
| |
We can now easily type
in the current directory to
cause everything to be built.
|
| |
You can see we have added an additional disconnected
target clean:. Targets can be run explictly on the command-line
like:
which removes all built files.
|
| |
Makefiles have far more uses than just building C
programs. Anything that needs to be built from
sources can employ a Makefile to make things
easier.
|