SEPARATE COMPILATION Which is better? a. 1 file with 500 lines of code b. 7 files with 500 lines of code Why? easier to understand more logical structure reuse of some parts easier for people to work on DECLARATIONS AND DEFINITIONS def: a *declaration* describes a name's type (or usage) with enough information to use it, it's a kind of specification def: a *definition* describes the details of an implementation in C a definition either: - allocates storage or - provides an implementation Declaration examples: extern int somefun(); int from_roman(char *num); static int three(); extern int counter; struct complex { double x, y; }; enum sign {positive, zero, negative}; typedef struct { double r,theta;} cmplx; Definition examples: int somefun() { return three(); } int from_roman(char* num) { int val = 0; /* ... */ return val; } static int three() { return 3; } int counter = 0; struct complex coordinates; enum sign the_sign = positive; cmplx i; RULES FOR DECLARATIONS AND DEFINITIONS There must be exactly one (1) definition of a name in a scope. (A program and a file are scopes in C, also blocks.) There can be multiple declarations of a name, as long as they are all the same. The *scope* of a declaration extends from its "point of declaration" to the end of the block or file in which it is declared. int i; { int i; i++ /* refers to inner one... */ } i++ // refers to the outer i FORWARD DECLARATIONS NEEDED IN C What does a compiler say about: void parseExpr() { /* ... */ parseTerm(); /* ... */ } void parseTerm() { /* ... */ parseExpr(); /* ... */ } How to fix it? add a declaration of parseTerm before its first use add to the front of the program void parseTerm(); SIMULTANEOUS DECLARATION AND DEFINITION For local variables and functions not used elsewhere: Example: int count = 0; void parseTerm() { /* ... */ } Every definition is also a declaration DEFAULT COMPILATION: ENTIRE PROGRAM gcc sayHelloProgram.c produces an executable in a.out What if there are multiple .c files? EXAMPLE ENTIRE PROGRAM // file sayHelloProgram.c #include #include extern void sayHello(); extern const char *who(); int main(int argc, char *argv[]) { sayHello(); return EXIT_SUCCESS; } void sayHello() { printf("Hello, %s!\n", who()); } const char *who() { return "class"; } // FILE sayHelloMain.c #include #include "sayHello.h" int main(int argc, char *argv[]) { sayHello(); return EXIT_SUCCESS; } // FILE sayHello.h #ifndef _SAYHELLO_H #define _SAYHELLO_H extern void sayHello(); #endif // FILE sayHello.c #include #include "sayHello.h" #include "who.h" void sayHello() { printf("Hello, %s!\n", who()); } // FILE who.h /* $Id: who.h,v 1.1 ... */ #ifndef _WHO_H #define _WHO_H extern const char *who(); #endif // FILE who.c /* $Id: who.c,v 1.2 ... */ #include ".h" const char *who() { return "class of modular programmers"; } SEPARATE COMPILATION Compile each .c file to an object file: gcc -c sayHelloMain.c sayHello.c who.c or gcc -c sayHelloMain.c gcc -c sayHello.c gcc -c who.c Use the -c option to compile a .c file into a .o file UNIX MAKE TOOL Rules in a file named "makefile" (or "Makefile") Makefile rules: # macros, definitions of names CC = gcc CFLAGS = -g -std=c17 -Wall OBJECTS = sayHelloMain.o sayHello.o who.o sayHelloMain.o: sayHelloMain.c sayHello.h $(CC) $(CFLAGS) -c sayHelloMain.c %.o: %.c %.h $(CC) $(CFLAGS) -c $< sayHello: $(OBJECTS) $(CC) $(CFLAGS) -o sayHello \ $(OBJECTS) LINKING def: *linking* brings together several separately compiled files into one executable Commands that do linking: ld, gcc What it does: puts together a bunch of separately compiled (.o) files, making an executable. EXAMPLES OF MODULARITY IN REAL LIFE Electric plugs, can plug in anything: - toaster, - TV - computer - phone charger - coffee maker Cooking ingredients: - meat (chicken, etc.) - butter - fruits - spices What makes a system modular? there's a standard interface API = application programming interface syntax + semantics EXAMPLES OF MODULARITY IN SOFTWARE SQL queries work on: - many different databases - on many kinds of computers - same meaning everywhere TCP/IP network protocol: - connects many different systems (different hardware, different OS) - clients include: browsers, email, ... Mathematical libraries: - computation of trig. functions - on many different computers - approximately same results MODULARITY IN SOFTWARE 2 roles: - client: uses the interface - implementation: provides the interface (computation) Agreed upon interface: - names and how they can be used (i.e., syntax and semantics) MODULES IN PROGRAMMING A module: in C: .h file and a .c file e.g., who.h and who.c in general: an interface (declarations) and an implementation (definitions) In programming want to hide decisions about: implementation (details) INFORMATION HIDING Some information changes fairly often during maintenance Ideally, information about decisions that are likely to change e.g.: data structures and related algorithms PROG. LANG. SUPPORT FOR INFORMATION HIDING Limit clients: - discovery of secrets (e.g., data structure decisions) - manipulation of secrets - creation of secrets Mechanisms: - function mechanisms can hide algorithms - scope limitations can hide names within blocks - export/import of names from modules - type limitations like range of an integer (short vs. long) INFO. HIDING IN C Modules are files Names private to module: are static Names available to clients: are extern (not static) EXAMPLE IN C: SET OF STRINGS // FILE string_set.h #ifndef _STRING_SET_H #define _STRING_SET #include // Initialize the string set extern void string_set_init(); // Put s into the set extern void string_set_add(const char *s); // Is s in the set? extern bool string_set_has(const char *s); // ... #endif ONE IMPLEMENTATION // FILE string_set.c #include #include #include #include #include "string_set.h" typedef struct element_s { const char *val; struct element_s *next; } element; static element *elements; // invariant: elements is // a singly-linked list with no cycles // invariant: no duplicates // in the list of elements // Initialize the string set void string_set_init() { elements = NULL; } // Put s into the set void string_set_add(const char *s) { if (!string_set_has(s)) { // s is not already in the list element *newelem = malloc(sizeof(element)); if (newelem == NULL) { fprintf(stderr, "No space!\n"); exit(EXIT_FAILURE); } newelem->val = s; newelem->next = elements; elements = newelem; } assert(string_set_has(s)); } // Is s in the set? extern bool string_set_has(const char *s) { element *p = elements; while (p != NULL) { if (strcmp(p->val, s) == 0) { return true; } p = p->next; } return false; } // ... ABSTRACT DATA TYPES def: an *abstract data type* (ADT) is a set of values and operations on them. Benefits: information hiding: can change data structures + algorithms hidden by the ADT - allows optimization of time/space - reduces changes in the code - allows reasoning about code without reasoning about entire program modularity, multiple people can work on different parts of the program