420 likes | 586 Views
Compilation / Pointers Debugging 101. Compilation in C/C++. hello.c. tmpXQ.i (C code). Compiler. Preprocessor. stdio.h. hello.o (object file). Preprocesser. A single-pass program that Include header files Expands macros Control conditional compilation Remove comments.
E N D
Compilation in C/C++ hello.c tmpXQ.i (C code) Compiler Preprocessor stdio.h hello.o (objectfile)
Preprocesser A single-pass program that • Include header files • Expands macros • Control conditional compilation • Remove comments
#include directive #include ”foo.h” • Include the file “foo.h”, from current directory #include <stdio.h> • Include the file “stdio.h” from the standard library directory (part of compiler installation)
complex.h struct Complex { double _real, _imag; }; Complex addComplex(Complex, Complex); Complex subComplex(Complex, Complex); ... complex.c #include <math.h> #include “complex.h” // implementation Complex addComplex(Complex a, Complex b) { … MyProg.c #include “complex.h” int main() { Complex c; … Modules & Header files Complex.c
Header files Header file contain • Definition of data types • 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: • Constants enum { FOO = 1; }; or const int FOO = 1; • Functions – inline functions (C++, later on)
#if directive • 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 <assert.h> // 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 <exception> • The exception allows to catch the event in the debugger
assert.h Important coding practice • Declare implicit assumptions • Sanity checks in code • 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 • Takes input C-code and produces machine code (object file) • gcc –c Main.c • Main.c Main.o • The object file does not contain all external references • It leaves names, such as “printf”, “addComplex”, etc. as undefined references
Complex.c Complex.o Main.c Main.o Preprocessor Compiler Linker Linking • Combines several object files into an executable file • No unresolved references Main libc.a
Link errors The following errors appear only at link time • Missing implementation > gcc -o Main Main.p Main.o(.text+0x2c):Main.c: undefined reference to `foo' • 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 • Memory is arrange in a sequence of addressable units (usually bytes) • sizeof( <Type> ) 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; … c i j x
Arrays Defines a block of consecutive cells int main() { int i; int a[4]; … i a[0] a[1] a[2] a[3]
Arrays • 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. • It is the programmer’s responsibility to check whether the index is out of bounds…
Arrays • 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; i j x 1
Pointers int main() { int i,j; int *x; // x points to an integer i = 1; x = &i; j = *x; x = &j; (*x) = 3; i j x 1 0x0100 0x0100
Pointers int main() { int i,j; int *x; // x points to an integer i = 1; x = &i; j = *x; x = &j; (*x) = 3; i j x 1 1 0x0100 0x0100
Pointers int main() { int i,j; int *x; // x points to an integer i = 1; x = &i; j = *x; x = &j; (*x) = 3; i j x 1 1 0x0104 0x0100
Pointers int main() { int i,j; int *x; // x points to an integer i = 1; x = &i; j = *x; x = &j; (*x) = 3; i j x 1 3 0x0104 0x0100
Pointers • Declaration <type> *p; p points to objects of type <type> • Pointer value *p = x; y = *p; *p refers to the object p points to • value pointer &x - the pointer to x
Example – the swap function Does nothing Works 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]! p a[0] a[1] a[2] a[3]
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 • 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[0] a[1] a[2] a[3] p q
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 • 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 101 • “Define” the bug --- reproduce it • Use debugger • Don’t panic --- think! • Divide & Conquer