The Preprocessor
- When dealing with multiple files, we need one or more header
files to define function signatures as well as structs, global variables,
typedefs, etc.
- The header file is loaded using a preprocessor directive #include
which loads the specified file
- Local header files have quotes around the name, and are relative to the current directory
- System header files have <> around the file name, and are found in a system-defined location (e.g., /usr/include/)
- The C preprocessor supports many other directives.
Defining constants and macros
Conditional Compilation
- The C preprocessor can also be used to "turn on" or "turn off" sections of code depending on whether something has been #define'd
#ifdef IS_WINDOWS
/* do something backwards */
....
# else
/* do regular code */
....
#endif
Conditional Compilation
- #ifndef allows for the reverse test. Commonly used to prevent header files from being loaded more than once.
/* local.h -- local function definitions, variables, types, etc. */
#ifndef LOCAL_LOADED
#define LOCAL_LOADED
#include <stdio.h>
#define PI the_whole_thing
int eat(int food);
#endif
Common use of conditionals
- Conditional compilation is often used in debugging, and for platform specific code
...
#ifdef DEBUGGING
printf("Code reached point 14; x=%d, y=%f\n", x, y);
#endif
Symbols can also be defined at compile time, not just in #define statements
gcc -D DEBUGGING mycode.c
Other conditions
- The preprocessor can also check the value of a constant expression
#if DEBUG_LEVEL == 1 /* show minimal debugging */
...
#elif DEBUG_LEVEL == 2 /* show more */
...
#elif DEBUG_LEVEL == 9 /* show max */
...
#else /* unsupported debugging level */
...
#endif
Defensive Programming
- The larger your project, the more difficult it will be to find subtle
errors
- The best approach is to code defensively
- Check every function for failure codes (especially system
calls)
- Initialize all variables, especially pointers
- Write testing harnesses for all functions
- Document carefully -- e.g., describe all assumptions for values
passed as parameters to your functions
- Verify that parameters are valid before using them
- Use assert(3c) liberally to enforce those assumptions
Using assert()
- We want coding errors to cause the app to fail
quickly so that they can be easily detected during
debugging.
- C provides a standard assert(3c) macro, which evalutes the
expression passed in, and if false, outputs an error message and calls
abort(3c)
#include <assert.h>
...
assert(status == 0);
Failures would look something like:
% ./program
source.c:114: failed assertion `status == 0'
Abort
%
When to use/not use assert()
- Use assert() anywhere that a coding error might cause an invalid condition
- e.g., when your code is called by someone else!
- or when one module calls another
- Don't use assert() to validate user input
- since assert causes the program to fail, rather than politely generating an appropriate error message and permitting the user to re-enter valid input
- Don't use assert() when you can recover gracefully
- Such as when trying to open a file without proper permissions
Looking Ahead
- Friday will be our first exam. Remember, one sheet of notes!
(Feel free to use mpage to make them small!)