Programming in C Project Organization Compiler Directives A Real C Program • Program that accepts one command line argument. • Treats the command line argument as a string, and reverses the letters in the string. • Prints out the the reversed string. 7/28/09 reverse.c - part 1 /** reverse.c ** inputs a single command line argument which is a string ** to be printed with its characters reversed.’ ** Use quotes if your string contains spaces ** reverse( ) uses dynamic memory so any size string is ok */ #include <stdio.h> /* printf */ #include <stdlib.h> /* malloc,free */ #include <string.h> /* strlen, strcpy */ /* user error message */ #define USAGE "Usage: reverse \"string to reverse in quotes\"" /* prototype for reverse */ void reverse( char *s); 7/28/09 reverse.c - part 2 /* main checks the number of args ** then just calls the function to do the work */ int main(int argc, char *argv[ ]) { if (argc < 2) { printf("%s\n", USAGE); exit(0); } char *string = argv[1]; reverse( string ); printf("%s\n", string); return 0; } 7/28/09 reverse.c // void reverse (char[ ] s) void reverse( char *s ) { char *buf; int i, sLength = strlen( s ); /* allocate memory -- why + 1 ??*/ buf = (char *)malloc( sLength + 1); /* copy string in reverse order in place */ for (i = 0; i < sLength; i++) buf[i] = s[sLength - i - 1]; buf[i]='\0'; // null terminate !! */ strcpy(s,buf); free(buf); } 7/28/09 // copy string back to s // free the memory Compiler directives Compiler directives begin with # and are interpreted by the preprocessor The most common directives are – #include • Used for file inclusion (See K&R 4.11.1) – #define • Used for constant/macro definitions (see K&R 4.11.2) – #if, #ifdef, #ifndef, #else, #elif, #endif • Used for conditional compilation (see K&R 4.11.3) 7/28/09 #include The #include directive is used to include the contents of another file into the file being compiled. It is usually used to include header (.h) files into the source. #include files may be nested so that one .h file may #include another .h file A line of the form #include <filename> causes replacement of that line by the entire contents of the file filename. The named file is searched for in implementation specific places, usually /usr/include on Linux Similarly, a line of the form #include “filename” first searches in the same directory as the source file, and if that search fails, continues searching as above. 7/28/09 #define A line of the form #define identifier token-sequence Causes the preprocessor to replace all occurrences of identifier with the token-sequence (except in quoted strings). Note that this is a text replacement. #defines are most commonly used to give names to “magic constants” For example #define MAXSIZE 100 replaces every occurrence of MAXSIZE with 100 as in int table[MAXSIZE]; 7/28/09 #define macros The #define directive may also be used to create a simple macro. Care must be taken when writing macros and when calling macros because the arguments are copied literally. For example, the simple macro below doesn’t work properly in all instances. #define SQUARE(x) x * x Assuming int z = 4, what is the value of y after the assignment int y = SQUARE( z - 1 ); Even when written correctly as #define SQUARE(x) (x) * (x) Calling the macro may result in erroneous side effects. Assuming again that int z = 4, what is the value of z after the assignment int y = SQUARE( ++z ) Nonetheless, macros like this can make code easier to read as we’ll see later 7/28/09 Importing #defines • The most common way to #define symbols is within your .c file – #define DEBUG – #define MAX 100 • It is also possible to “import” #defines on the compiler command line – gcc -o myProg -DDEBUG -DMAX=100 myProg.c • You cannot use the command line to redefine a symbol already defined in your .c file 7/28/09 assert( ) • The C library provides a macro named assert( ) which can be very helpful for debugging • The parameter to assert is any boolean expression -- assert( expression ); – If the boolean expression is true, nothing happens and execution continues on the next line – If the boolean expression is false, a message is printed to stderr and your program terminates • To use assert( ), you must #include <assert.h> 7/28/09 Using assert( ) • Use assert( ) to verify an array index int scores[100]; int k = <some complicated calculation > assert( k >= 0 && k < 1000 ); • Use assert( ) to check for NULL pointers int *ptr = malloc( 4 * sizeof( int ) ); assert( ptr != NULL); • Use assert( ) to check functions that shouldn’t fail int errorCode = foo ( ); assert( errorCode == 0); 7/28/09 /* but NOT assert(foo( ) == 0; */ Conditional Compilation • The preprocessor directives #ifdef (read as “if defined”), #ifndef (read as “if not defined”), #if, #else, #elif (read as “else if”), and #endif (together with #defines) can be used to control which lines in a course file are compiled and which are not. Each directive appears on a separate line. • Each #if and subsequent #elif is evaluated as true (1) or false (0) based on the definition (or lack of definition) of the identifier that follows. Lines that follow a false (0) evaluation are skipped. • Conditional compilation is frequently used to avoid multiple inclusions of the same header (.h) file, compiling lines used for debugging, and (in system level .h files) to compile certain lines based on the type of system on which the program is being compiled. 7/28/09 Conditional Compilation for header files Header (.h) files should only be included once in a source file to avoid compilation errors. Suppose the file myheader.h is #included into some source file. The .h files should be “guarded” as follows #ifndef MYHEADER_H #define MYHEADER_H .... ... #endif /* same as #if !defined(MYHEADER_H) */ How does this work? Whenever the compiler includes myheader.h, the #ifndef checks to see if the identifier MYHEADER_H has be #defined. The first time myheader.h is included, MYHEADER_H will not have been defined so the lines in the header file (in particular #define MYHEADER_H) will be included into the source file. Thereafter, when the compiler subsequently tries to include myheader.h, the #ifndef will evaluate to false (because MYHEADER_H is now defined) and all lines in myheader.h will be skipped, avoiding duplicate inclusion. 7/28/09 Conditional Compilation for Debugging One simple way of debugging a program is to insert printf( ) statements are strategic points in the code. When the program is run, the output of these printf( ) statements help you debug your program. When the program is bug free, these printf( ) statements are removed from your code. Rather than removing the printf( ) statements by editting your code, conditional compilation can be used to allow or prevent them from being executed. 7/28/09 debug.c #include <stdio.h> static void debug(char *string) { #ifdef DEBUG printf( “%s\n”, string ); #endif } int main (int argc, char *argv[] ) { int i, count, sum = 0; debug( “Command Line arg is: “); debug(argv[1]); count = atoi( argv[1] ); debug( “Entering for loop”); for (i = 0; i < ; i++) { count += i; } debug (“exited for loop”); return 0; } 7/28/09 Program organization • main( ) is generally defined in its own .c file and generally just calls helper functions • Program-specific helper functions in another .c file – If there are very few helpers, they can be in the same file as main( ) • Reusable functions in their own .c file – Group related functions in the same file • Each struct or union – Defined in a separate .h file – Related functions in a separate .c file – File names should be indicative of the struct name 7/28/09 Variable Scope and Lifetime • The scope of a variable refers to that part of a program that may refer to the variable. • The lifetime of a variable refers to the time in which a variable occupies a place in memory • The scope and lifetime of a variable are determined by how and where the variable is defined 7/28/09 Global Variables • Global (external) variables are defined outside of any function, near the top of your .c file. – May be used anywhere in the .c file in which they are defined. – Exist for the duration of your program – May be used by any other .c file in your program that declares them as “extern” (unless also defined as static) – Static global variables may only be used in the .c file that declares them – “extern” declarations for global variables should be placed into a header file 7/28/09 Local variables • Local variables are defined within the opening and closing braces of a function, loop, if-statement, etc. Function parameters are local to the function. – Are usable only within the braces in which they are defined – Exist only during the execution of the block unless also defined as static – Static local variables retain their values for the duration of your program. Usually used in functions, they retain their values between calls to the function. 7/28/09 Function Scope • All functions are external because C does not allow nesting of function definitions. – So no “extern” declaration is needed – All functions may be called from any .c file in your program unless they are also declared as static. • Static functions may only be used within the .c file in which they are defined 7/28/09 variableScope.c - part 1 #include <stdio.h> // extern definition of randomInt and prototype for getRandomInt #include “randomInt.h” /* a global variable that can only be used by functions in this .c file */ static int inputValue; /* a function that can only be called by other functions in this .c file */ static void inputPositiveInt( char *prompt ) { /* init to invalid value to enter while loop */ inputValue = -1; while (inputValue <= 0) { printf( "%s", prompt); scanf( "%d", &inputValue); } } 7/28/09 variableScope.c - part 2 /* main is the entry point for all programs */ int main( ) { /* local/automatic variables that can only be used in this function and that are destroyed when the function ends */ int i, maxValue, nrValues; inputPositiveInt("Input max random value to generate: "); maxValue = inputValue; inputPositiveInt("Input number of random ints to generate: "); nrValues = inputValue; for (i = 0; i < nrValues; i++) { getRandomInt( maxValue ); printf( “%d: %d\n", i + 1, randomInt ); } return 0; /* successful completion */ } 7/28/09 randomInt.c /* ** ** ** a global variable to be used by code in other .c files. This variable exists until the program ends Other .c files must declare this variable as "extern" holds the random number that was generated */ int randomInt; /* a function that can be called from any other function ** returns a random integer from 1 to max, inclusive */ void getRandomInt( int max ) { /* a local variable that can only be used inside this function, ** but persists between calls to this function */ static long lastRandom = 100001; lastRandom = (lastRandom * 125) % 2796203; randomInt = (lastRandom % max) + 1; } 7/28/09 randomInt.h #ifndef RANDOMINT_H #define RANDOMINT_H // global variable in randomint.c // set by calling getRandomInt( ) extern int randomInt; // prototypes for function in randomInt.c void getRandomInt(int max ); #endif 7/28/09 End of Class 6 7/28/09