Include Is Used to Include C Code from Include (.H) Files

Include Is Used to Include C Code from Include (.H) Files

clark / cs3843 / syllabus / lecture notes / programming assignments / homework / set up
Preprocessor
The C preprocessor is used to extend the notation of C. Some capabilities:
  • #include is used to include C code from include (.h) files
  • #define is used to define constants or expandable macros
  • #if is used for conditional logic within the preprocessor.
The C preprocessor runs automatically when you compile. If you want to see what it produced, you can try
gcc -E my.c
or
cpp my.c
Sometimes, the output from the C preprocess is difficult to read. You might want to redirect the output to sed and indent commands to improve it.
gcc -E my.c | sed '/^\#/d' | indent -st -i4 > my_x.c / You have already seen the #include preprocessor statement which has two forms:
#include <filename
#include "filename"
Note that the compiler supports a -I option for you to provide a path to search for include files.
You have also seen #define for defining constants (i.e., simple macros).
#define ERR_INPUT_FILENAME "Input filename is invalid, found "
#define
We have already used the form of #define which allows us to name a constant. C considers those to be simple macros or macros without arguments. / #define PI 3.14159
#define SSN_SIZE 9
#define SSN_PLUS_1_SIZE SSN_SIZE + 1
#define macros with arguments
#define can also be used to define macros having arguments. This can be used to extend the capabilities of C.
C doesn't support an exponentiation operator. It could be convenient to create a SQUARE macro having arguments.
In the definition, it may be useful to surround the referenced arguments with parentheses to avoid some problems. / Define a macro to square something
#define SQUARE(X) X*X// careful this can cause problems
// Example 1 - computing area of a circle
dCircleArea = PI * SQUARE(dRadius);
// that would expand to
dCircleArea = 3.14159 * dRadius * dRadius;
// Example 2
dCircleArea = PI * SQUARE(dRadius+1);
//expands to
dCircleArea = 3.14159 * dRadius+1 * dRadius+1;
How do we fix that?
#define SQUARE(X) (X)*(X)// careful this can cause problems
//Example 2 would expand to
dCircleArea = 3.14159 * (dRadius+1) * (dRadius+1);
//Example 3 - 1 divided by the square of dLambda
dResult = 1 / SQUARE(dLambda);
//expands to
dResult = 1 / (dLambda)*(dLambda);
How de we fix that?
#define SQUARE(X) ((X)*(X))// much better
// Example 3 would now expand to
dResult = 1 / ((dLambda)*(dLamda));
Warning! When defining a macro having arguments, make certain you do not put a space after the macro's name. It will consider that a definition of a constant macro (see SSN_PLUS_1_SIZE above) which doesn't have arguments. The resulting compilation errors are typically difficult to understand. / #define SQUARE (X) ((X)*(X))
This would not create the macro SQUARE with X as its argument; instead, it creates a constant macro square (without arguments) with its value being (X) ((X)*(X)).
One of the more popular uses of #define macros having arguments is to reduce coding for error handling and provide extra diagnostics.
If a macro's definition is fairly long, lines can be continued by ending them with a backslash.
The following predefined macros can help with diagnostics:
__FILE__expands the name of the current C source file
__LINE__expands to the current line number / Suppose you wanted to know where an error was found when showing error diagnostics.
#define ERR_EXIT(MSG,INFO) {\
printf("ERROR: %s %s\n\tEncountered at line %d in file %s\n" \
, MSG, INFO\
, __LINE__\
, __FILE__);\
exit(1); }

if (pszFileName == NULL)
ERR_EXIT("Missing -i switch", "");
// Would expand to (without a compiler issue)
if (pszFileName == NULL)
{
printf(ERROR: %s %s\n\tEncountered at line %d in file %s\n"
,"Missing -i switch", ""
, 36
,"full path/myprogram.c");
exit(1);
};
Note: that definition has a problem when used in some C code.
#if, #ifndef, and #ifdef
The C preprocessor supports conditionals that are resolved during preprocessor execution. The conditionals resemble C conditionals.
Reasons for conditionals:
  1. The same declarations of variables, typedefs or prototypes may be needed in several include files. The preprocessor conditional can help avoid errors associated with redefining those.
  2. A program may have debug information that was very useful during development and testing, but will impact performance when a program is moved to production. The preprocessor conditional can be used to leave out that debug information from the generated executable.
  3. A program may need to use different code depending on particulars of a particular machine, operating system or compiler. The preprocessor conditional can help provide code that can run anywhere, but has those kind of particulars.
Each of those C preprocessor if statements must be ended by an #endif. Those also support an optional #else. / // For reason #1, we typically code this for include file xyz.h
#ifndef XYZ_H
#define XYZ_H
typedef struct xyz
{
blah blah blah
} xyz;
#endif
Keeping debug statements in your code
Some debug capabilities are too useful to simply delete from your code; however, you don't want them to impact execution for production code. This can be solved by using #ifdef DEBUG_ON. Note that we could have called it any name (e.g., SPURS). / #define DEBUG_ON

int main(…)
{
//some code
#ifdef DEBUG_ON
fprintf(stderr, "debug: at %d, myVar is %s\n"
, __LINE__
, myVar);
#endif
// some more code
#ifdef DEBUG_ON
fprintf(stderr, "debug: at %d myOther is %s\n"
, __LINE__
, myOther);
#endif
It might be useful to have a macro which prints debug information to standard error only when DEBUG_ON is defined.
In this example, we have coded three DEBUG macros:
DEBUG(S)prints information about a string variable S
DEBUGD(S)prints information about a double variable S
DEBUGL(S)prints information about a long variable
If we executed DEBUG(szSSN) it would print a message like the following:
debug: at line 36, szSSN is '' / #defineDEBUG_ON
#ifndefDEBUG_ON
// define the DEBUG macros as null values (i.e. doesn't do anything)
#define DEBUG(S)
#define DEBUGD(S)
#define DEBUGL(S)
#else
#defineDEBUG(S) fprintf(stderr, "debug: at line %d, %s is '%s'\n" \
, __LINE__\
, #S\
, S)
#defineDEBUGD(S) fprintf(stderr, "debug: at line %d, %s is %10.2lf\n" \
, __LINE__\
, #S\
, S)
#defineDEBUGL(S) fprintf(stderr, "debug: at line %d, %s is %ld\n" \
, __LINE__\
, #S\
, S)
#endif
Recognizing different machines or OS
Microsoft compilers define the constants _WIN32 and _WIN64. If those are undefined, we aren't running compiling on either 32 bit or 64 bit Windows. / // partial timestamp.h include file
#include <time.h>
// If running on Microsoft Windows, redefine gettimeofday to one
// that invokes ms windows APIs.
#if defined(_WIN32) || defined(_WIN64)
#define gettimeofday(tv,tz) ms_gettimeofday(tv,tz)
#include <WinSock2.h>
struct timezone
{
long tz_minuteswest; // minutes W of Greenwich
int tz_dsttime; // type of daylight savings correction
};
int ms_gettimeofday(struct timeval *ptv, struct timezone *ptz);
#else
// include the standard Unix include file for timestamps
#include <sys/time.h>
#endif
Earlier, we said that the ERR_EXIT macro has a problem. What is it? What would cause a compilation error in the expansion? / #define ERR_EXIT(MSG,INFO) {\
printf("ERROR: %s %s\n\tEncountered at line %d in file %s\n" \
, MSG, INFO\
, __LINE__\
, __FILE__);\
exit(1); }

if (rc!=0)
ERR_EXIT("Unknown problem for ", pszXYZ);
else
migrate(pszXYZ);
// Would expand to
if (rc!=0)
{
fprintf(stderr,"ERROR: %s %s\n\tEncountered at line %d in file %s\n"
,"Unknown problem for ", pszXYZ
, 36
,"full path/myprogram.c");
exit(1);
};
else
migrate(pszXYZ);
ERR_EXIT expands to multiple statements which must be surrounded by {}, but it is more natural to reference ERR_EXIT followed by a semicolon which causes the expansion to end with "};". One solution around this problem is to use a do { … } while statement which must end with a semicolon. / #defineERR_EXIT(MSG,INFO) do {\
fprintf(stderr,"ERROR: %s %s\n\tEncountered at line %d in file %s\n" \
, MSG, INFO\
, __LINE__\
, __FILE__);\
exit(1); } while (0)
if (rc!=0)
ERR_EXIT("Unknown problem for ", pszXYZ);
else
migrate(pszXYZ);
// Would expand to
if (rc!=0)
do
{
fprintf(stderr,"ERROR: %s %s\n\tEncountered at line %d in file %s\n"
,"Unknown problem for ", pszXYZ
, 36
,"full path/myprogram.c");
exit(1);
} while(0);
else
migrate(pszXYZ);
Warning: as with other programming tools, macros can be dangerous. It may be more difficult to understand a problem which was caused by macro expansion than straight coding. Do NOT create tricky macros.