. Compilation / Pointers Debugging 101
Compilation in C/C++ hello.c Preprocessor Compiler stdio.h tmpXQ.i (C code) hello.o (object file)
Preprocesser A single-pass program that u Include header files u Expands macros u Control conditional compilation u Remove comments
#include directive #include ”foo.h” u Include the file “foo.h”, from current directory #include u Include the file “stdio.h” from the standard library directory (part of compiler installation)
Modules & Header files Complex.c struct Complex { double _real, _imag; }; Complex addComplex(Complex, Complex); Complex subComplex(Complex, Complex);... complex.h #include #include “complex.h” // implementation Complex addComplex(Complex a, Complex b) { … complex.c #include “complex.h” int main() { Complex c; … MyProg.c
Header files Header file contain u Definition of data types u Declarations of functions & constants That are shared by multiple modules. #include directive allows several modules to share the same set of definitions/declarations
#define directive #define FOO 1 … int x = FOO; is equivalent to … int x = 1;
#define with arguments #define square(x) x*x b = square(a); is the same as b = a*a;
#define -- cautions #define square(x) x*x b = square(a+1); c = square(a++); Is it what we intended? b = a+1*a+1; // b = 2*a+1; c = a++*a++; // c = a*a; a+=2;
#define #define directive should be used with caution Alternative to macros: u Constants enum { FOO = 1; }; or const int FOO = 1; u Functions – inline functions (C++, later on)
#if directive u Allows to have conditional compilation #if defined(DEBUG) // compiled only when DEBUG exists printf(“X = %d\n”, X); #endif
#if – header safety Complex.h: struct Complex { … MyStuff.h: #include “Complex.h” Main.c #include “MyStuff.h” #include “Complex.h” Error: Complex.h:1: redefinition of `struct Complex'
#if – header safety Complex.h (revised): #if !defined(COMPLEX_H) #define COMPLEX_H struct Complex { … #endif Main.c: #include “MyStuff.h” #include “Complex.h” // no error this time
Preprocessor We can test what the preprocessor does > gcc –E hello.c will print the C code after running preprocess
assert.h #include // Sqrt(x) - compute square root of x // Assumption: x non-negative double Sqrt(double x ) { assert( x >= 0 ); // aborts if x < 0 … If the program violates the condition, then assertion "x >= 0" failed: file "Sqrt.c", line 7 u The exception allows to catch the event in the debugger
assert.h Important coding practice u Declare implicit assumptions u Sanity checks in code u Check for violations during debugging/testing Can we avoid overhead in production code?
assert.h #undef assert // procedure that actually prints error message void _assert(char* file, int line, char* test); #ifdef NDEBUG #define assert(e) ((void)0) #else #define assert(e) ((e) ? (void)0 : __assert(__FILE__, __LINE__, #e)) #endif
Compilation u Takes input C-code and produces machine code (object file) gcc –c Main.c Main.c Main.o u The object file does not contain all external references It leaves names, such as “printf”, “addComplex”, etc. as undefined references
Linking u Combines several object files into an executable file No unresolved references Main Preprocessor Compiler Complex.c Complex.o Main.c Main.o Linker libc.a
Link errors The following errors appear only at link time u Missing implementation > gcc -o Main Main.p Main.o(.text+0x2c):Main.c: undefined reference to `foo' u Duplicate implementation > gcc -o Main Main.o foo.o foo.o(.text+0x0):foo.c: multiple definition of `foo' Main.o(.text+0x38):Main.c: first defined here
Memory Arrangement u Memory is arrange in a sequence of addressable units (usually bytes) sizeof( ) return the number of units it takes to store a type. sizeof(char) = 1 sizeof(int) = 4 (on most of our machines)
Memory int main() { char c; int i,j; double x; … cijx
Arrays Defines a block of consecutive cells int main() { int i; int a[4]; … ia[0]a[1]a[2]a[3]
Arrays u C does not provide any run time checks int a[4]; a[-1] = 0; a[4] = 0; This will compile and run (no errors) …but can lead to unpredictable results. u It is the programmer’s responsibility to check whether the index is out of bounds…
Arrays u C does not provide array operations int a[4]; int b[4]; … a = b; // illegal if( a == b ) // illegal …
Array Initialization int arr[3] = {3, 4, 5}; // Good int arr[] = {3, 4, 5}; // Good - The same int arr[4] = {3, 4, 5}; // Good - The last is 0 int arr[2] = {3, 4, 5}; // Bad int arr[2][3] = {{2,5,7},{4,6,7}}; // Good int arr[2][3] = {2,5,7,4,6,7}; // Good - The same int arr[3][2] = {{2,5,7},{4,6,7}}; // Bad int arr[3]; arr = {2,5,7}; // Bad - array assignment only in initialization
Pointers int main() { int i,j; int *x; // x points to an integer i = 1; x = &i; j = *x; x = &j; (*x) = 3; ijx 1
Pointers int main() { int i,j; int *x; // x points to an integer i = 1; x = &i; j = *x; x = &j; (*x) = 3; ijx 1 0x0100
Pointers int main() { int i,j; int *x; // x points to an integer i = 1; x = &i; j = *x; x = &j; (*x) = 3; ijx 1 0x0100 1
Pointers int main() { int i,j; int *x; // x points to an integer i = 1; x = &i; j = *x; x = &j; (*x) = 3; ijx 1 0x0100 0x01041
Pointers int main() { int i,j; int *x; // x points to an integer i = 1; x = &i; j = *x; x = &j; (*x) = 3; ijx 1 0x0100 0x01043
Pointers u Declaration *p; p points to objects of type u Pointer value *p = x; y = *p; *p refers to the object p points to u value pointer &x - the pointer to x
Example – the swap function Does nothingWorks void swap(int a, int b) { int temp = a; a = b; b = temp; } …. int main() { int x, y; x = 3; y = 7; swap(x, y); // now x==3, y==7 …. void swap(int *pa, int *pb) { int temp = *pa; *pa = *pb; *pb = temp; } …. int main() { int x, y; x = 3; y = 7; swap(&x, &y); // x == 7, y == 3 …
Pointers & Arrays int *p; int a[4]; p = &a[0]; *(p+1) = 1; // assignment to a[1]! pa[1]a[2]a[3]a[0]
Pointers & arrays Arrays are essentially constant pointers int *p; int a[4]; p = a; // same as p = &a[0] p[1] = 102; // same as *(p+1)=102; *(a+1) = 102; // same p++; // p == a+1 == &a[1] a = p; // illegal a++; // illegal
Pointers & Arrays int foo( int *p ); and int foo( int a[] ); Are declaring the same interface u In both cases, a pointer to int is being passed to the function foo
Pointer Arithmetic int a[4]; int *p = a; char *q = (char *)a; // Explicit cast // p and q point to the same location p++; // increment p by 1 int (4 bytes) q++; // increment q by 1 char (1 byte) a[1]a[2]a[3]a[0] q p
Pointer arithmetic int FindFirstNonZero( int a[], int n ) { int *p; for( p = a; p < a+n && (*p) == 0; p++ ) ; return p-a; } Same as int FindFirstNonZero( int a[], int n ) { int i; for( i = 0; i < n && a[i] == 0; i++ ) ; return i; }
void * void *p defines a pointer to undetermined type int j; int *p = &j; void* q = p; // no cast needed p = (int*)q ; // cast is needed
NULL pointer u Special value: uninitialized pointer int *p = NULL; … if( p != NULL ) { … }
NULL pointer int *p = NULL; *p = 1; Will compile… … but will lead to runtime error
Debugging “Define” the bug --- reproduce it 2. Use debugger 3. Don’t panic --- think! 4. Divide & Conquer