Document 17548303

advertisement
clark
cs3843
syllabus
lecture notes
programming assignments
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
#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.
homework
set up
You have already seen the #include preprocessor statement which
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 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
problems
// careful this can cause
// 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 problem
//Example 2 would expand to
dCircleArea = 3.14159 * (dRadius+1) * (dRadius+1);
//Example
dResult =
//expands
dResult =
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.
3 - 1 divided by the square of dLambda
1 / SQUARE(dLambda);
to
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));
#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);
};
#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.
Note: that definition has a problem when used in some C code.
// 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:
, __LINE__
, myVar);
#endif
// some more code
#ifdef DEBUG_ON
fprintf(stderr, "debug:
, __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
DEBUG(S) prints information about a
S
DEBUGD(S) prints information about a
S
DEBUGL(S) prints information about a
macros:
string variable
double variable
long variable
If we executed DEBUG(szSSN) it would print a message like
the following:
debug: at line 36, szSSN is ''
at %d, myVar is %s\n"
at %d myOther is %s\n"
#define DEBUG_ON
#ifndef DEBUG_ON
// define the DEBUG macros as null values (i.e. doesn't do anything)
#define DEBUG(S)
#define DEBUGD(S)
#define DEBUGL(S)
#else
#define DEBUG(S) fprintf(stderr, "debug: at line %d, %s is '%s'\n" \
, __LINE__
\
, #S
\
, S)
#define DEBUGD(S) fprintf(stderr, "debug: at line %d, %s is %10.2lf\n" \
, __LINE__
\
, #S
\
, S)
#define DEBUGL(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.
Earlier, we said that the ERR_EXIT macro has a problem.
What is it? What would cause a compilation error in the
expansion?
// 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
#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);
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.
// 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);
#define ERR_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.
Download