This tutorial introduces you to the C preprocessor for defining macros and including files, and also the idea that a program can span multiple source files. This tutorial is a UNIX variant of the original CSSE1000 C Programming Tutorial 4.
It is assumed that you are familiar with the material covered in previous tutorials and the associated readings.
The following textbook sections are relevant:
You may wish to create a new directory for this tutorial. You may type (cut and paste) each program in a separate C file or you may just use one C file and just overwrite your previous program each time (except in the example below where we'll be writing a program that spans multiple files).
All C program lines which begin with the symbol # (hash) are "preprocessor directives". The C preprocessor is a program that runs before the C compiler which performs textual substitution - e.g. replacement of defined constants and inclusion of other files. We've already seen one preprocessor directive: #include - which means to insert the specified file at this point. We'll revisit #include shortly, but first we'll consider the #define preprocessor directive for defining constants and macros.
A symbolic constant is a name (symbol) that is replaced by some sequence of characters, for example:
#define PI 3.14159265
#define TRUE 1
#define FALSE 0
#define COURSE "COMP1300 / COMP2302"
These lines define the symbolic constants PI, TRUE, FALSE, and COURSE. Anywhere these names appear in the program (except inside double-quoted strings), they will be replaced by the appropriate symbol value (sequence of characters). Try running the following program:
#include <stdio.h> #define PI 3.14159265 int main() { double r; /* double precision floating point number */ printf("Radius Circle Area\n"); for(r = 1.0; r<= 10.0; r+= 1.0) { printf("%-6g %7g\n", r, PI*r*r); } return 0; }
Note that the definition does not end with a semicolon. If a semicolon is included it will be substituted also (and in this case will cause a syntax at the point of inclusion. Notice the format used to print out the results: %-6g means print out a floating point number (g) using 6 characters (6) and left aligned (-) within those characters. (Try changing the format specifications and re-running the program to see the effect of these options.)
It is conventional (but not required) that C preprocessor constants are given names that are all uppercase.
It is possible to define macros with arguments so that the replacement text is different for different calls of a macro. For example, consider the macro definition
#define SQUARE(x) x*x
The line
printf("10 squared is %d\n", SQUARE(10));
would be changed to
printf("10 squared is %d\n", 10*10);
before compilation.
What will the following program output?
#include <stdio.h> #define SQUARE(x) x*x int main() { int i; for(i=0; i<10; i++) { printf("%d squared is %d\n", i+1, SQUARE(i+1)); } return 0; }
Why doesn't it output what you expect it to?
Try replacing the #define line with
#define SQUARE(x) ((x)*(x))
and see what the output of the program is. Why did the original program output what it did?
It is possible to make macros span multiple lines by using the backslash character at the end of a line. Try running the following program (make sure you don't have any spaces after the backslashes):
#include <stdio.h> /* Macro to swap two elements of type t */ #define SWAP(t,x,y) { t temp; \ temp = x; \ x = y; \ y = temp; \ } int main() { int a = 5; int b = 10; double c = 45.0; double d = 100.0; printf("a = %d, b = %d, c = %g, d = %g\n", a, b, c, d); SWAP(int, a, b); SWAP(double, c, d); printf("a = %d, b = %d, c = %g, d = %g\n", a, b, c, d); return 0; }
1. Write a macro SUM(x,y,z) which adds three numbers together. Write a program, which uses this macro, which reads in three numbers from the user and prints their sum.
The system include file limits.h defines some useful constants. Try running the following program to discover what the maximum values that can be represented with various types are. Notice the different formatting directives passed to printf (hd is used for a short integer and ld is used for a long integer and lld is used for a long long integer). Note that different types are different lengths on different platforms. Integers may be 32 bits on some platforms and only 16 on others. Longs may be 32 bits on some platforms and 64 bits on others. Long long integers are not standard, so they may not even exist on some platforms.
#include <stdio.h> #include <limits.h> int main() { printf("Maximum integer: %d\n", INT_MAX); printf("Maximum short integer: %hd\n", SHRT_MAX); printf("Maximum long integer: %ld\n", LONG_MAX); printf("Maximum long long integer: %lld\n", LLONG_MAX); printf("Maximum unsigned integer: %u\n", UINT_MAX); return 0; }
It is possible (and often useful) to write programs that span multiple source files. For example, we'll create a small "library" that has an ask_for_integer() function.
Save the following program in a file called prompt_for_number.c
#include <stdio.h> int ask_for_integer(char prompt[]) { int a; printf("%s", prompt); scanf("%d",&a); return a; }
Note that there is no main() function here - we can't run this as a program. We will build an object file that contains an executable version of this function that we can link into a larger program. Run the command
gcc -c prompt_for_number.c
The -c option means compile, but do not link. This will generate a file called prompt_for_number.o - an object code file.
In order to use the library function in another program it is necessary to write a function prototype as we saw last week. It is conventional to do this in a header file which is then included in files that want to use functions in that "library". Create a file called prompt_for_number.h which contains the following :
/* ** prompt_for_number.h */ int ask_for_integer(char prompt[]);
This header file can now be included in other programs. Let's create an example called example.c (make sure this is in the same directory as your other files):
#include <stdio.h> #include "prompt_for_number.h" int main() { int a,b; a = ask_for_integer("Enter first number: "); b = ask_for_integer("Enter second number: "); printf("Sum of the two is %d\n", a+b); return 0; }
Note the second #include statement - the filename is enclosed in double quotes rather than <>. Double quotes are used for programmer header files, whereas angled brackets <> are used for system header files.
If you tried compiling just this file to create an executable, e.g. with the command:
gcc -o example example.c
you would get an error message something like:
Undefined first referenced symbol in file ask_for_integer /var/tmp/ccdNJaj6.o ld: fatal: Symbol referencing errors. No output written to example collect2: ld returned 1 exit status
which indicates the linker (ld) does not know where to find the "ask_for_integer" function (which is defined in the prompt_for_number.o object file). If we use the command:
gcc -o example example.c prompt_for_number.o
it will create the executable "example" from the files example.c and prompt_for_number.o. The same result could be obtained by separately compiling the main source file and then linking the object files together:
gcc -c example.c gcc -o example example.o prompt_for_number.o
Usually, we would use a Makefile to help manage the building of multi-file projects. We'll look at Makefiles later in the course.
You should try running the resulting executable:
./example
2. Try adding a function ask_for_double(char prompt[]) to the library and modify the example program to use this function also.