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.