600.120 Intermediate Programming, Spring 2017* Misha Kazhdan *Much of the code in these examples is not commented because it would otherwise not fit on the slides. This is bad coding practice in general and you should not follow my lead on this.
Outline Preprocessor directives Code correctness
Preprocessor directives Lines starting with # indicate preprocessor directives #include includes a file at the current location all the contents of stdio.h go here printf( hello world\n ); printf( hello world\n );
Preprocessor directives Lines starting with # indicate preprocessor directives #include #define associate a value to a preprocessor variable (no = sign) or just declare the existence of a preprocessor variable all the contents of stdio.h go here printf( %s: %f\n, 3.1415926, PI ); #define PI 3.1415926 #define PI_STR PI #define MISHA_DEBUG printf( %s: %f\n, PI_STR, PI );
Preprocessor directives Lines starting with # indicate preprocessor directives #include #define #undef undeclare the existence of a preprocessor variable #undef MISHA_DEBUG printf( %s: %f\n, PI_STR, PI );
Preprocessor directives Lines starting with # indicate preprocessor directives #include #define / #undef #if / #elif / #else / #endif Conditionally include the code all the contents of stdio.h go here printf( Using first code\n ); #define CODE 1 #if CODE==1 printf( Using first code\n ); #elif CODE==2 printf( Using second code\n ); #else // CODE!=1 && CODE!=2 fprintf( stderr, No code\n ); #endif // CODE
Preprocessor directives Lines starting with # indicate preprocessor directives #include #define / #undef #if / #elif / #else / #endif #ifdef / #else / #endif #ifndef / #else / #endif Conditionally include the code all the contents of stdio.h go here printf( Using first code\n ); #define MISHA_DEBUG #ifdef MISHA_DEBUG printf( debug mode ); #else //!MISHA_DEBUG printf( release mode\n ); #endif // MISHA_DEBUG
Preprocessor directives Lines starting with # indicate preprocessor directives #include #define / #undef #if / #elif / #else / #endif #ifdef / #else / #endif #ifndef / #else / #endif #error force a compiler error #undef GOOD_CODE #ifndef GOOD_CODE #error bad code #endif //!GOOD_CODE printf( hello\n ); >> gcc -std=c99 -pedantic -Wall -Wextra foo.c foo.c:6:2: error: #error "bad code\n"; #error "bad code\n"; ^~~~~ >>
Preprocessor directives Lines starting with # indicate preprocessor directives #include #define / #undef #if / #elif / #else / #endif #ifdef / #else / #endif #ifndef / #else / #endif #error And many more
Preprocessor directives Lines starting with # indicate preprocessor directives You can nest preprocessor directives all the contents of stdio.h go here printf( debugging 1\n ); #define MISHA_DEBUG #define DEBUG_MODE 1 #ifdef MISHA_DEBUG #if DEBUG_MODE==1 printf( debugging 1\n ); #else // DEBUG_MODE!=1 printf( unknown debug mode\n ); #endif #else //!MISHA_DEBUG printf( hello world ); #endif // MISHA_DEBUG
Preprocessor directives Lines starting with # indicate preprocessor directives You can nest preprocessor directives Can be very useful while working on code: You can work on new changes without discarding the old ones You can specify that different parts of the code should be activated / deactivated together It makes it hard to read the code Remove all the unnecessary directives (unused code) once you ve got the new version up and running
Outline Preprocessor directives Code correctness
Code correctness During execution, your code may end up in a bad state from which you cannot recover Blame the world: The assumptions about the state of the system are false Bad user input insufficient arguments int main( int argc, char* argv[] ) if( argc<2 ) fprintf( stderr, ) ; exit( 0 );
Code correctness During execution, your code may end up in a bad state from which you cannot recover Blame the world: The assumptions about the state of the system are false Bad user input: insufficient arguments file can t be accessed int main( int argc, char* argv[] ) if( argc<2 ) fprintf( stderr, ) ; exit(0); FILE* fp = fopen( argv[1], r ); if( fp==null ) fprintf( stderr, ), exit( 0 );
Code correctness During execution, your code may end up in a bad state from which you cannot recover Blame the world: The assumptions about the state of the system are false Bad user input insufficient arguments file can t be accessed wrong argument type / value int main( int argc, char* argv[] ) if( argc<2 ) fprintf( stderr, ) ; exit(0); int i = atoi( argv[1] ); if( i<=0 ) fprintf( stderr, ) ; exit( 0 );
Code correctness During execution, your code may end up in a bad state from which you cannot recover Blame the world: The assumptions about the state of the system are false Bad user input insufficient arguments file can t be accessed wrong argument type / value Bad system state insufficient memory int main( int argc, char* argv[] ) if( argc<2 ) fprintf( stderr, ) ; exit(0); int i = atoi( argv[1] ); if( i<=0 ) fprintf( stderr, ) ; exit( 0 ); char* str = malloc( i ); if(!str ) fprintf( stderr, ) ; exit( 0 );
Code correctness During execution, your code may end up in a bad state from which you cannot recover Blame the world: The assumptions about the state of the system are false Bad user input Bad system state Print out a meaningful error message and: from main: return with a failure (non-zero) value from a function: exit
Code correctness During execution, your code may end up in a bad state from which you cannot recover Blame the world: The assumptions about the state of the system are false Blame yourself: The code didn t do what it should have void sort_ints( int* arr, size_t sz ) int a[] = 11, 7, 9, 5, 8, 4, 2 ; sort_ints( a, sizeof(a) / sizeof(int) ); if( a[0]>a[1] )
Code correctness During execution, your code may end up in a bad state from which you cannot recover Blame the world: The assumptions about the state of the system are false Blame yourself: The code didn t do what it should have Include the assert.h header file assert the validity of a test If the argument is true, nothing happens Otherwise, the code aborts and a core dump file is generated #include <assert.h> void sort_ints( int* arr, size_t sz ) return; int a[] = 11, 7, 9, 5, 8, 4, 2 ; sort_ints( a, sizeof(a) / sizeof(int) ); assert( a[0]<a[1] ); >>./a.out a.out: foo.c:11: main: Assertion `a[0]<a[1]' failed.abort (cored dumped) >> You can use gdb to debug the core dump
Code correctness Note: This code tests if the first two elements are sorted #include <assert.h> void sort_ints( int* arr, size_t sz ) return; int a[] = 11, 7, 9, 5, 8, 4, 2 ; sort_ints( a, sizeof(a) / sizeof(int) ); assert( a[0]<a[1] );
Code correctness Note: This code tests if the first two elements are sorted Better code tests that all the elements are sorted #include <assert.h> void sort_ints( int* arr, size_t sz ) return; int a[] = 11, 7, 9, 5, 8, 4, 2 ; sort_ints( a, sizeof(a) / sizeof(int) ); assert( a[0]<a[1] ); #include <assert.h> void sort_ints( int* arr, size_t sz ) int issorted( const int * arr, size_t sz ) for( int i=0 ; i<sz-1 ; i++ ) if( arr[i]>arr[i+1] ) return 1; int a[] = 11, 7, 9, 5, 8, 4, 2 ; sort_ints( a, sizeof(a) / sizeof(int) ); assert( issorted( a, sizeof(a) / sizeof(int) );
Notes on assert assert is defined as a macro, not a function #include <assert.h> void sort_ints( int* arr, size_t sz ) int issorted( const int * arr, size_t sz ) for( int i=0 ; i<sz-1 ; i++ ) if( arr[i]>arr[i+1] ) return 1; int a[] = 11, 7, 9, 5, 8, 4, 2 ; sort_ints( a, sizeof(a) / sizeof(int) ); assert( issorted( a, sizeof(a) / sizeof(int) );
Notes on assert assert is defined as a macro, not a function Once our code is working we can stop the assertion by defining the preprocessor variable NDEBUG #define NDEBUG #include <assert.h> void sort_ints( int* arr, size_t sz ) int issorted( const int * arr, size_t sz ) for( int i=0 ; i<sz-1 ; i++ ) if( arr[i]>arr[i+1] ) return 1; int a[] = 11, 7, 9, 5, 8, 4, 2 ; sort_ints( a, sizeof(a) / sizeof(int) ); assert( issorted( a, sizeof(a) / sizeof(int) );
Notes on assert assert is defined as a macro, not a function Once our code is working we can stop the assertion by defining the preprocessor variable NDEBUG This removes the whole assertion clause so that the argument is not evaluated issorted doesn t get called so the code runs more quickly #define NDEBUG #include <assert.h> void sort_ints( int* arr, size_t sz ) int issorted( const int * arr, size_t sz ) for( int i=0 ; i<sz-1 ; i++ ) if( arr[i]>arr[i+1] ) return 1; int a[] = 11, 7, 9, 5, 8, 4, 2 ; sort_ints( a, sizeof(a) / sizeof(int) ); assert( issorted( a, sizeof(a) / sizeof(int) );
Notes on assert assert is defined as a macro, not a function Once our code is working we can stop the assertion by defining the preprocessor variable NDEBUG This removes the whole assertion clause so that the argument is not evaluated issorted doesn t get called so the code runs more quickly Beware: The argument of assert should not do something useful! #define NDEBUG #include <assert.h> void sort_ints( int* arr, size_t sz ) int issorted( const int * arr, size_t sz ) for( int i=0 ; i<sz-1 ; i++ ) if( arr[i]>arr[i+1] ) return 1; int sortandtest( int* arr, size_t sz ) sort( arr, sz ); return( issorted( arr, sz ) ); int a[] = 11, 7, 9, 5, 8, 4, 2 ; assert( sortandtest( a, sizeof(a) / sizeof(int) );
Testing Until now, our program testing has been applied to an entire program Run with prescribed input and check if the output matches what we expect This is called end-to-end testing Verifies that program will run as expected under real-world conditions End-to-end testing is important, but tracking down bugs this way gets more difficult as programs get larger and more complex It doesn t help identify where these are broken when the output is wrong
Unit testing IDEA: Test each unit, or module, of a program separately For each function we write, we create another function which tests it A test function is typically fairly simple, and just runs a number of tests on the subject function, one after another Tests typically consist of feeding the subject function a known input, and verifying that it returns the appropriate result Each input/desired output pair is called a test case Want numerous test cases, so passing all test cases is convincing evidence that subject function works correctly Test with a wide variety: good inputs, bad inputs, boundary/corner cases
Unit testing It s generally hard/impossible to prove that code is correct. Unit tests help convince us that our functions ( building blocks ) behave correctly, or else they help us locate errors quickly Useful for adapting /modifying code e.g. If function foo1 calls function foo2, we can check that changes to foo2 don t invalidate the functionality of foo1 Writing unit tests shouldn t be an afterthought; resulting code is often better when we test as we go (and it takes less time to write overall) It s turtles all the way down: We re still left with the problem of confirming that our unit tests are correct Hopefully they are simple enough that there is less of an opportunity for error.
Coding Session