Syntax a bit more 1 Forward declaration typedef
Syntax – a bit more 1
Forward declaration typedef struct node; typedef struct list { node* _head; int _num. Of. Nodes; char* _name; }list ; typedef struct node { void* _data; list* _my. List; }node; int main() {. . . 2
Forward declaration typedef struct node; typedef struct list { node _head; int _num. Of. Nodes; char _name[10]; }list ; struct node { void* _data; list* _my. List; }; int main() { 3 . . .
Statements - conditional if (condition) //statement or block else //statement or block 4 switch (integer value) { case 3: //statement or block break; //optional. Otherwise case 5: //statement or block break; default: //statement or block } fall-through until ‘break; ’ or ‘}’!
Statements - conditional goto bla; whee: //code here bla: //code there DANGER: SPAGHETTI CODE! Avoid whenever possible. Q: When to use goto? A: There are cases. e. g. break from a lot of nested loops, forward jump to error handler 5
Errors handling
The problem: include <stdio. h> void sophisticated. Algorithm (char* name) { FILE * fd = fopen (name); // using the file // for an algorithm //. . . } int main() { char name[100]; scanf ("%s", name); sophisticated. Algorithm (name); //. . . } 7
OOP (java / C++) solution: try { File. Input. Stream fstream = new File. Input. Stream(name); Data. Input. Stream in = new Data. Input. Stream(fstream); while (in. available() !=0) { System. out. println (in. read. Line()); } in. close(); } catch (Exception e) { System. err. println("File input error"); } 8
How can it be done in C?
Errors types: • Bugs: • Deterministic errors • Not dependant on the program inputs • You assert they will never happen • Exceptions: • Originate from the program inputs and environment • Input streams • Memory allocations • … • May happen from time to time 10
Catching bugs -- assert #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 11
Using assert: • Terminates the program continuation. • Good for debugging and logic examination. • User of library function can not decide what to do in case of an error. • Discarded in NDEBUG mode 12
C exception handling strategies: Detecting the errors: 1. “Catch the exception” before it occurs. 2. Use function return value to indicate errors. 3. Use global variables to indicate that errors occurred and their identity. 4. Develop an ‘exception-catching- like’ mechanism (will not be discussed in this course). Handling the errors: • May include printing error messages. • May include program termination. 13
Printing error messages: • Use the standard errors stream (stderr) • Relevant functions (examples in the following slides): • fprintf(stderr, “format string”, …) • perror • strerr (with errno) • stdout and stderr can be redirected separately: >> my. Prog > output. File >& error. File 14
Program termination • exit(int status) terminates the program: #include <stdio. h> /* fprintf, fopen */ #include <stdlib. h> /* exit, EXIT_FAILURE */ int read. File (){ FILE * p. File; p. File = fopen ("myfile. txt", "r"); if (p. File==NULL){ fprintf (stderr, "Error opening file"); exit (EXIT_FAILURE); } else{ /* file operations here */ } return EXIT_SUCCESS; } int main(){ int status = read. File(); return status; } 15 Also ok without the ‘else’
Return status • ‘ 0’ -- success • other values -- failure (most common: ‘ 1’/ ’-1’) • stdlib. h defines the macros: • #define EXIT_SUCCESS 0 • #define EXIT_FAILURE 1 16
Find the error before it occurred #include <stdio. h> #include <stdlib. h> #include “my. Funcs. h” dividend ÷ divisor = quotient int main() { int dividend = 20; int divisor; int quotient; scanf(“%d”, &devisor); if (divisor == 0){ fprintf(stderr, "Division by zero! Exiting. . . n"); return 1; } quotient = divide(dividend, divisor); printf("Value of quotient : %dn", quotient); return 0; } 17
Return values: #include <stdio. h> int sophisticated. Algorithm (char* name) { FILE * fd = fopen (name); if( in == NULL ) { return -1; // indicate an abnormal // termination of the // function } 18 // do your sophisticated stuff here return 0; // indicate a normal // termination of the function }
Special return values indicate errors: int main() { if(sophisticated. Algorithm(name) == -1) { // the exceptional case } else { // the normal case } } 19
Return values: User of a library function can decide what to do in case of an error. But: • A dedicated parameter is needed to indicate the error. • We need a separate value for each error type. • Requires checking after each function call. �No separation of regular code from the errors checking 20
Modify a global variable int g_division. Error; int divide(int dividend, int quotient){ division. Error = 0; if (divisor == 0){ g_division. Error = 1; return 1; } return dividend / divisor; } int main(){ int c = divide(20, 0); if (g_divison. Error == 1){ fprintf(stderr, "Division by zero! Exiting. . . n"); return EXIT_FAILURE; }. . . 21
The standard library approach: Combination of return value and global variable to indicate errors The idea: Separate between function return code and error description. • Function return just 0 in case of success or -1 in case of error. • A global variable holds the specific error code (or message) describing the last error that has occurred. 22
Example 1: #include <stdio. h> #include <errno. h> // for the global variable errno #include <string. h> // for strerror int main () { FILE * p. File; p. File = fopen ("unexist. ent", "r"); if (p. File == NULL) fprintf (stderr, "Error: %lu, %sn", errno, strerror(errno)); . . . • 23 We could also use perror
Example 2: #include <stdio. h> #include <string. h> #include <errno. h> int main () { FILE * p. File; const char* file. Name="read. Only. File. txt"; p. File = fopen (file. Name, "w"); if (p. File == NULL) perror("An error has occurred while trying to open the file for writing"); . . . 24
C exceptions: google: C exceptions will lead to methods / libraries that implement some kind of exceptions Exist in C++ 25
Program Design 26
Interfaces A definition of a set of functions that provide a coherent module (or library) �Data structure (e. g. , list, binary tree) �User interface (e. g. , drawing graphics) �Communication (e. g. , device driver) 27
Interface - modularity Hide the details of implementing the module from its usage �Specification – “what” �Implementation – “how” 28
Interface – information hiding Hide “private” information from outside �The “outside” program should not be able to use internal variables of the module �Crucial for modularity Resource management �Define who controls allocation of memory (and other resources) 29
Example interface - str. Stack A module that allows to maintain a stack of strings Operations: �Create new �Push string �Pop string �Is. Empty �Free 30
Example interface - str. Stack #ifndef _STRSTACK_H #define _STRSTACK_H struct Str. Stack; typedef struct Str. Stack; Str. Stack* Str. Stack. New(); void Str. Stack. Free(Str. Stack** stack); // This procedure *does not* duplicate s void Str. Stack. Push(Str. Stack* stack, char* s); // return NULL if the stack is empty char *Str. Stack. Pop( Str. Stack* stack ); // Check if the stack is empty int Str. Stack. Is. Empty(Str. Stack const* stack); 31 #endif // _STRSTACK_H
Implementation of str. Stack Decision #1: data structure �Linked list �Array (static? dynamic? ) �Linked list of arrays �… We choose linked list for simplicity 32
Implementation of str. Stack Decision #2: Resource allocation • Duplicated strings on stack or keep pointer to original? • If duplicate, who is responsible for freeing them? We choose not to duplicate --- leave this choice to user of module 33
Implementation of Str. Stack #include <assert. h> #include <stdlib. h> #include <stdio. h> #include "Str. Stack. h" typedef struct Str. Stack. Link { char* str; struct Str. Stack. Link_t *next; } Str. Stack. Link; struct Str. Stack { Str. Stack. Link* top; }; 34 void Str. Stack. Free(Str. Stack** stack) { while (!Str. Stack. Is. Empty(*stack)) { Str. Stack. Pop(*stack); } free(*stack); *stack=NULL; }
Implementation of Str. Stack 35 Str. Stack* Str. Stack. New() { Str. Stack* Stack = (Str. Stack*) malloc(sizeof(Str. Stack)); if (Stack != NULL) { Stack->top = NULL; } else { printf("out of memory, cannot create stackn"); } return Stack; } int Str. Stack. Is. Empty(Str. Stack const* stack) { assert( stack != NULL ); return stack->top == NULL; } void Str. Stack. Push(Str. Stack* stack, char* s) { assert( stack != NULL ); Str. Stack. Link *p = (Str. Stack. Link*) malloc(sizeof(Str. Stack. Link)); if (p == NULL) { printf("out of memory, cannot push a string to stackn"); return; } p->str = s; p->next = stack->top; stack->top = p; }
Implementation of Str. Stack char* Str. Stack. Pop(Str. Stack* stack) { char *s; Str. Stack. Link *p; assert( stack != NULL ); if (stack->top == NULL) { return NULL; } s = stack->top->str; p = stack->top; stack->top = p->next; free(p); return s; } 36
Using Str. Stack #include "Str. Stack. h" char * Read. Line() { . . . } //A function to read a line 37 int main() { char *line; Str. Stack *stack = str. Stack. New(); while ((line = readline()) != NULL) { str. Stack. Push(stack, line); } while ((line = str. Stack. Pop(stack)) != NULL) { printf("%sn", line); free(line); } str. Stack. Free(&stack); return 0; }
Interface Principles Hide implementation details 1. Hide data structures 2. Don’t provide access to data structures that might be changed in alternative implementation 3. A “visible” detail cannot be later changed without changing code using the interface! 38
Interface Principles Use small set of “primitive” actions 1. Provide to maximize functionality with minimal set of operations 2. Do not provide unneeded functions “just because you can” 3. How much functionality? Two approaches: Minimal (For few users, don't waste your time) Maximal (When many users will use it e. g. OS) 39
Interface Principles Don’t reach behind the back 1. Do not use global variables unless you must. 2. Don't have unexpected side effects! 3. Use comments if you assume specific order of operations by the user. 40
Interface Principles Consistent Mechanisms Do similar things in a similar way �strncpy(dest, source, num) �memcpy(dest, source, num) 41
Interface Principles Resource Management 1. Free resource at the same level it was allocated – the one which allocates the resource is responsible to free it 2. Assumptions about resources 1. File for writing 2. Size of allocated memory 3. “Constness” etc… 42
Pointers to functions
Pointers to Functions int avg(int num 1, int num 2) { return ( num 1 + num 2 ) / 2; } int (* func)(int, int); // a ptr variable func = &avg; func = avg; // assignment // same int result = avg(20, 30); result = (*func)(20, 30); // invoke it using the ptr result = func(20, 30); // same 44
What is this syntax? Function declaration is the same as variable declaration: int a, *pa; //int, pointer to int fa(), (*pfa)(); //function, pointer to // a function. int * pfa(); //Is a function // returning pointer to int And typedef follows the same syntax: typedef int ta; typedef int (*tpfa)(); // tpfa is the type of a // pointer to a // function without input // parameters, returning int 45
Function pointers as function arguments //<pt 2 Func> is a pointer to a function which returns an int and takes a float and two chars void Pass. Ptr(int (*pt 2 Func)(float, char)) { int result = (*pt 2 Func)(12, 'a', 'b'); } // execute example code void Pass_A_Function_Pointer() { Pass. Ptr(&Concat. Num. Char); } 46
Function that returns a pointer to a function // Get. Ptr 1 is a function that gets const char as input and returns pointer to function that gets two floats as input and returns a float (*Get. Ptr 1(const char op. Code))(float, float) { if(op. Code == '+') return &Plus; else return &Minus; } // define a function pointer and initialize it to NULL float (*pt 2 Function)(float, float) = NULL; pt 2 Function=Get. Ptr 1('+'); float ans = (*pt 2 Function)(4. 5 f, 6. 5 f); 47
typedef is your friend! typedef float(*pt 2 Func)(float, float); pt 2 Func Get. Ptr 2(const char op. Code) { if(op. Code == '+') return &Plus; else return &Minus; } 48
Example: Numerical Integrator 49
Example: Numerical Integrator double numerical. Integration( double a, double b, double (*func)(double), int k ) { double delta = (b - a)/k; double sum = 0; for(double x = a+0. 5*delta; x < b ; x += delta) { sum += (*func)(x); } return sum*delta; } 50
Example Suppose we implement an interface of a list of ints: struct Int. List; // Allocates a new list Int. List* int. List. New(); 51
Example typedef void (*func. Int)( int x, void* Data ); // Apply Func to each element of the list void int. List. MAPCAR(Int. List* List, func. Int Func, apply a function to each of the elements 52 void* Data);
Example: struct Int. List; typedef struct Int. List; Int. List* int. List. New(); void int. List. Free( Int. List* List ); void int. List. Push. Front( Int. List* List, int x); void int. List. Push. Back( Int. List* List, int x); int. List. Pop. Front( Int. List* List ); int. List. Pop. Back( Int. List* List ); int. List. Is. Empty( Int. List const* List); typedef void (*func. Int)( int x, void* Data ); void int. List. MAPCAR( Int. List* List, func. Int Func, void* Data ); 53
Implementation of MAPCAR Important concept of generic programming in C void int. List. MAPCAR(Int. List* List, func. Int Func, void* Data) { Int. List. Node* p; for (p=List->start; p!=NULL; p=p->next) { (*Func)(p->value, Data); } } 54 Think about the assumptions that int. List. MAPCAR makes
Usage of MAPCAR typedef struct List. Stats { int n; int sum. Of. Squares; } List. Stats; void Record. Statistics(int x, void* Data) { List. Stats *s = (List. Stats*) Data; s->n++; s->sum += x; s->sum. Of. Squares += x * x; } void int. List. Stats(Int. List* List, double* avg, double* var) { List. Stats = { 0, 0, 0 }; int. List. MAPCAR(List, Record. Statistics, &Stats); if (Stats. n > 0) { *avg = Stats. sum / (double) Stats. n; *var = Stats. sum. Of. Squares / (double) Stats. n - (*avg) * (*avg); } else { *avg = 0; *var = 0; } } 55
“Generic” interface MAPCAR is a uniform way of performing computations over a list of elements �The given function provides the different functional element Pointers to functions provide a way to write code that receives functions as arguments 56
Generic data-structures 57
Generic data-structures are data-structures that can hold data of any type (or, at least, of several types). • The specific type that the instance of the data-structure holds is determined during run-time. • The main tool C provides for generic data-structures implementation is: void* 58
memcpy Before we begin to discuss implementation of generic stack, let us introduce the function memcpy. void *memcpy(void *destination, const void *source, size_t num); • memcpy copies a block of memory of specific size from one address to another address. • memcpy doesn’t know the type of variable(s) being copied. • The main challenges: • How to iterate void*. • No pointer-arithmetics is defined for void* • How to derefrence the pointers 59
Possible implementation of memcpy void *memcpy(void *destination, const void *source, size_t num) { char *d = (char*) destination; char *s = (char*) source; int i; for(i=0; i<num; ++i) { //pointer arithmetics for char* is done with //units of sizeof(char) == 1 byte d[i]=s[i]; } } Why is it part of the standard library? 60
Back to generic stack We would like our stack to: • hold any type (same type to all of the stack nodes ). • allocate its own memory for the data it holds. We would like to support the following operations: • create new stack. • pop element from the stack head. • push element to the stack head. • check if the stack is empty. • free the stack. 61
Generic stack data structures: typedef struct Generic. Stack. Link { void *_data; //pointer to anything struct Node *_next; } Generic. Stack. Link; typedef struct Stack { Node size_t *_top; _element. Size; //we will need that // for memcpy } Stack; … Think about how to “generalize” Str. Stack 62
Variadic functions 63
Variadic functions A variadic function is a function with variable number of arguments. Example: printf • printf(“Hello worldn”); // 1 argument • printf(“The value of i is: %dn”, i); // 2 arguments • printf(“The value of i is: %d and its address is: %pn”, i, &i); // 3 arguments 64
Variadic function definition <return type> <function name>(<first parameter>, …) Examples: //std lib. function • int printf(const char *format, …) //function we define • int sum. Ints(int integer. Num, …) 65
The main challenge: • Since we do not know the function parameters in advance, we can’t give them names. • We have to develop a technique to access the variables based on their type and place relative to the first parameter. 66
stdarg. h To define variadic functions we should include the header stdarg. h • definition of the type: va_list • A type for iterating the arguments (most likely void*) • definition of the macros: • va_start – start iterating the argument lists with va_list • va_arg – retrieve the current argument • va_end – free the va_list 67
Example #include <stdarg. h> int sum. Ints(int args. Num, …) { va_list ap; int i; va_start(ap, args. Num); //now we point to the place // right after the first argument for (i = 0; i<args. Num; ++i) sum += va_arg(ap, int); //access current argument and // move ap to the next argument va_end(ap); // free ap return sum; } We have to know that it is an int // in main: sum. Ints(5, 1, 1, 2, 3, 4) //returns 11 sum. Ints(2, 7, 1) 68 //returns 8
Libraries
Libraries Library is a collection of functions that you may want to use • Written and compiled by you • Or by someone else Examples: �C’s standard libraries �Math library �Graphic libraries Libraries may be composed of many different object files 70
Libraries 2 kinds of libraries: Static libraries: �linked with your executable at compilation time �standard unix suffix: . a (windows: . lib) Shared (dynamic) libraries: �loaded by the executable at run-time �standard unix suffix: . so (windows: . dll) 71
static vs. shared Shared libraries pros: 1. Smaller executables 2. Multiple processes share the code 3. No need to re-compile executable when libraries are changed (can cause to “dll hell”) Static libraries pros: 1. Independent of the presence/location of the libraries 2. Independent of the versions of the libraries 3. Less linking overhead on run-time 72
Static libraries – creating libraries Using the static library libdata. a: gcc main. o -ldata -o prog Creating the library libdata. a: ar rcs libdata. a data. o stack. o list. o • ar is like tar – archive of object files • rcs are 3 relevant flags (read ar man pages) 73
Static libraries in makefile LIBOBJECTS = data. o stack. o list. o libdata. a: ${LIBOBJECTS} ar rcs libdata. a ${LIBOBJECTS}. . . OBJECTS = main. o another. o CC = gcc prog: ${OBJECTS} libdata. a ${CC} ${OBJECTS} –ldata –o prog 74
Static libraries – my library #include <utils. h> // Library header int main () { foo(); // foo is a function of the // 'utils' library . . . } 75
Static libraries – using libraries Compilation: gcc -Wall –c main. c –o main. o Linking: gcc main. o -L /usr/lib/my. Lib/ -lutils -o app http: //www. adp-gmbh. ch/cpp/gcc/create_lib. html 76
The linking process: Objects vs. static libraries Objects: �The whole object file linked to the executable, even when its functions not used. �Two function implementations – will cause error. Libraries: �Just symbols (functions) which not found in the obj files are linked. �Two function implementations: first in the obj file, and second in library => The first will be used. 77
Dynamic Libraries Library creation: Compilation: gcc –c -Wall –f. PIC utils. c *PIC – position independent code. Output: utils. o Linking: gcc –shared utils. o –o libutils. so GCC assumes that all dynamic libraries start with ‘lib’ and end with. so Usage: http: //www. cprogramming. com/tutorial/shared-libraries-linux-gcc. html True dynamic loading (e. g. browser plugins) service is provided by OS (dlopen on linux, loadlibrary on windows) 78
Optimization 79
What smart people say about it. . . Premature optimization is the root of all evil (or at least most of it) in programming – Donald Knuth (check the “humor” part in his wiki)
So, what to do? Check if you need to optimize Profile - check where to optimize (gprof for gcc - https: //www. cs. utah. edu/dept/old/texinfo/as/gprof. html) Remember to “turn off” debugging (gcc -D NDEBUG) Check what your compiler can do for you on your specific hardware (-O 3, -march=pentium 4, etc…) We'll learn a bit more about optimization in labcpp
Registers Register ( )אוגר - is a small amount of storage available on the CPU There are few registers per CPU
Register Variables The register keyword specifies that the variable is to be stored in a CPU register, if possible. A recommendation to the compiler register int i; Provides quick access to commonly used values Experience has shown that the compiler usually knows much better than humans what should go into registers and when
Volatile - Motivation Sometimes variables can be modified externally (not from the currently compiled piece of code) The compiler cannot “guess” it
Volatile example Before optimization int* addr; void foo(void) { addr = INPUT_ADDRESS; *addr = 0; while (*addr != 255); }
Volatile example After optimization int* addr; void foo(void) { addr= INPUT_ADDRESS; *addr = 0; while (1); }
Volatile example After optimization using volatile int* addr; void foo(void) { addr= INPUT_ADDRESS; *addr = 0; while (*addr != 255); }
Volatile - Summary Variables declared to be volatile will not be optimized by the compiler This is due to our assumption that their value can change at any time
Cache-friendly coding • Fast memory is expensive => We have very little of it. • Goal: to achieve good performance with little fast memory. • Method: memory is organized in a hierarchic order: • Registers • Cache (several levels) • RAM • Virtual memory • Search from the fastest to the slowest memory. • Fetch important memory before it is used! • When a given location is accessed, most likely its neighborhood will be accessed more in the future. 89
Cache-friendly coding. Iterating 2 D array C requires “row - major ordering” //efficient better? int i, j; int arr[ROWS_NUM][COLS_NUM]; for (i = 0; i<ROWS_NUM; ++i){ for (j = 0; j<COLS_NUM; ++j){ arr[i][j] = i*j; } } //not efficient better? int arr[ROWS_NUM][COLS_NUM]; for (i = 0; i<COLS_NUM; ++i){ for (j = 0; j<ROWS_NUM; ++j){ arr[j][i] = i*j; } } 90
- Slides: 90