The C Language the C Language C History BCPL C History C History
Total Page:16
File Type:pdf, Size:1020Kb
The C Language C History Currently, the most Developed between 1969 and 1973 along The C Language commonly-used language for with Unix embedded systems Due mostly to Dennis Ritchie COMS W4995-02 ”High-level assembly” Designed for systems programming Prof. Stephen A. Edwards Very portable: compilers • Fall 2002 exist for virtually every Operating systems Columbia University processor • Utility programs Department of Computer Science Easy-to-understand • Compilers compilation • Filters Produces efficient code Fairly concise Evolved from B, which evolved from BCPL BCPL C History C History Martin Richards, Cambridge, 1967 Original machine (DEC Many language features designed to reduce memory PDP-11) was very small: Typeless • Forward declarations required for everything • Everything a machine word (n-bit integer) 24K bytes of memory, 12K • Designed to work in one pass: must know everything used for operating system • Pointers (addresses) and integers identical • Written when computers No function nesting Memory: undifferentiated array of words were big, capital equipment PDP-11 was byte-addressed Natural model for word-addressed machines Group would get one, • Now standard Local variables depend on frame-pointer-relative develop new language, OS addressing: no dynamically-sized automatic objects • Meant BCPL’s word-based model was insufficient Strings awkward: Routines expand and pack bytes to/from word arrays Euclid’s Algorithm in C Euclid’s Algorithm in C Euclid on the PDP-11 .globl gcd GPRs: r0–r7 int gcd(int m, int n ) “New syle” function int gcd(int m, int n ) Automatic variable .text r7=PC, r6=SP, r5=FP { declaration lists { Allocated on stack gcd: int r; number and type of int r; when function jsr r5, rsave Save SP in FP arguments. while ((r = m % n) != 0) { while ((r = m % n) != 0) { entered, released L2: mov 4(r5), r1 r1 = n Originally only on return sxt r0 sign extend m = n; listed return type. m = n; div 6(r5), r0 r0, r1 = m ÷ n Parameters & n = r; Generated code did n = r; mov r1, -10(r5) r = r1 (m % n) automatic variables not care how many jeq L3 if r == 0 goto L3 } } accessed via frame arguments were mov 6(r5), 4(r5) m = n return n; return n; pointer actually passed, mov -10(r5), 6(r5) n = r } } Ignored and everything was n Other temporaries jbr L2 a word. m also stacked L3: mov 6(r5), r0 r0 = n FP ! PC jbr L1 Arguments are non-optimizing compiler r ! SP L1: jmp rretrn return r0 (n) call-by-value Euclid on the PDP-11 Pieces of C C Types .globl gcd Very natural Types and Variables Basic types: char, int, float, and double .text mapping from • Definitions of data in memory gcd: C into PDP-11 Meant to match the processor’s native types jsr r5, rsave instructions. L2: mov 4(r5), r1 Expressions • Natural translation into assembly Complex addressing modes sxt r0 • Arithmetic, logical, and assignment operators in an div 6(r5), r0 make frame-pointer-relative • Fundamentally nonportable: a function of processor infix notation mov r1, -10(r5) accesses easy. architecture jeq L3 mov 6(r5), 4(r5) Another idiosyncrasy: Statements registers were mov -10(r5), 6(r5) • Sequences of conditional, iteration, and branching jbr L2 memory-mapped, so taking L3: mov 6(r5), r0 address of a variable in a instructions jbr L1 register is straightforward. L1: jmp rretrn Functions • Groups of statements invoked recursively Declarators Struct bit-fields Code generated by bit fields # unsigned int b1 = flags.b Declaration: string of specifiers followed by a declarator Aggressively packs data into memory movb flags, %al struct { struct { shrb 5, %al basic type unsigned int a : 5; movzbl %al, %eax unsigned int baud : 5; z}|{ unsigned int b : 2; andl 3, %eax static unsigned int (*f[10])(int, char*)[10]; | {z } | {z } unsigned int div2 : 1; unsigned int c : 3; movl %eax, -4(%ebp) specifiers declarator unsigned int use_external_clock : 1; } flags; } flags; # flags.c = c; void foo(int c) { movl flags, %eax Declarator’s notation matches that of an expression: use it Compiler will pack these fields into words. unsigned int b1 = movl 8(%ebp), %edx to return the basic type. flags.b; andl 7, %edx Implementation-dependent packing, ordering, etc. Largely regarded as the worst syntactic aspect of C: both flags.c = c; sall 7, %edx } andl -897, %eax pre- (pointers) and postfix operators (arrays, functions). Usually not very efficient: requires masking, shifting, and read-modify-write operations. orl %edx, %eax movl %eax, flags C Unions Layout of Records and Unions Layout of Records and Unions Like structs, but only stores the most-recently-written Modern processors have byte-addressable memory. Modern memory systems read data in 32-, 64-, or 128-bit field. 0 chunks: union { 1 3 2 1 0 int ival; 2 7 6 5 4 float fval; 11 10 9 8 char *sval; 3 } u; 4 Reading an aligned 32-bit value is fast: a single operation. Useful for arrays of dissimilar objects Many data types (integers, addresses, floating-point 3 2 1 0 numbers) are wider than a byte. Potentially very dangerous: not type-safe 7 6 5 4 16-bit integer: 1 0 Good example of C’s philosophy: Provide powerful 11 10 9 8 mechanisms that can be abused 32-bit integer: 3 2 1 0 Layout of Records and Unions Layout of Records and Unions C Storage Classes /* fixed address: visible to other files */ Slower to read an unaligned value: two reads plus shift. Most languages “pad” the layout of records to ensure int global static; alignment restrictions. /* fixed address: only visible within file */ 3 2 1 0 static int file static; struct padded { 7 6 5 4 /* parameters always stacked */ int x; /* 4 bytes */ int foo(int auto param) 11 10 9 8 { char z; /* 1 byte */ /* fixed address: only visible to function */ static int func static; short y; /* 2 bytes */ 6 5 4 3 /* stacked: only visible to function */ char w; /* 1 byte */ int auto i, auto a[10]; }; /* array explicitly allocated on heap (pointer stacked) */ double *auto d = SPARC prohibits unaligned accesses. x x x x malloc(sizeof(double)*5); MIPS has special unaligned load/store instructions. y y z = Added padding /* return value passed in register or stack */ return auto i; x86, 68k run more slowly with unaligned accesses. w } malloc() and free() malloc() and free() malloc() and free() Library routines for managing the heap More flexible than (stacked) automatic variables Memory usage errors so pervasive, entire successful int *a; More costly in time and space company (Pure Software) founded to sell tool to track a = (int *) malloc(sizeof(int) * k); them down malloc() and free() use non-constant-time algorithms a[5] = 3; Purify tool inserts code that verifies each memory access free(a); Two-word overhead for each allocated block: • Pointer to next empty block Reports accesses of uninitialized memory, unallocated memory, etc. Allocate and free arbitrary-sized chunks of memory in any • Size of this block order Publicly-available Electric Fence tool does something Common source of errors: similar Using uninitialized memory Using freed memory Not allocating enough Indexing past block Neglecting to free disused blocks (memory leaks) malloc() and free() Dynamic Storage Allocation Dynamic Storage Allocation #include <stdlib.h> Rules: struct point {int x, y; }; int play with points(int n) Each allocated block contiguous (no holes) { # free() struct point *points; Blocks stay fixed once allocated points = malloc(n*sizeof(struct point)); malloc() int i; # malloc( ) for ( i = 0 ; i < n ; i++ ) { Find an area large enough for requested block points[i].x = random(); points[i].y = random(); Mark memory as allocated } free() /* do something with the array */ Mark the block as unallocated free(points); } Simple Dynamic Storage Allocation Dynamic Storage Allocation Simple Dynamic Storage Allocation Maintaining information about free memory Simplest: Linked list S N S S N S S N S S N The algorithm for locating a suitable block Simplest: First-fit The algorithm for freeing an allocated block # free() # malloc( ) Simplest: Coalesce adjacent free blocks S S N S S N S S N Dynamic Storage Allocation malloc() and free() variants Fragmentation Many, many other approaches. ANSI does not define implementation of malloc()/free(). malloc( ) seven times give Other “fit” algorithms Memory-intensive programs may use alternatives: Segregation of objects by size Memory pools: Differently-managed heap areas free() four times gives More clever data structures Stack-based pool: only free whole pool at once Nice for build-once data structures Single-size-object pool: malloc( ) ? Fit, allocation, etc. much faster Need more memory; can’t use fragmented memory. Good for object-oriented programs On unix, implemented on top of sbrk() system call (requests additional memory from OS). Fragmentation and Handles Automatic Garbage Collection Automatic Garbage Collection Standard CS solution: Add another layer of indirection. Remove the need for explicit deallocation. Challenges: Always reference memory through “handles.” System periodically identifies reachable memory and How do you identify all reachable memory? frees unreachable memory. (Start from program variables, walk all data structures.) Reference counting one approach. Circular structures defy reference counting: ha hb hc Mark-and-sweep another: cures fragmentation. A B *a *b *c # compact Used in Java, functional languages, etc. The original Neither is reachable, yet both have non-zero reference Macintosh did counts. ha hb hc this to save memory. Garbage collectors often conservative: don’t try to collect *a *b *c everything, just that which is definitely garbage. Arrays Lazy Logical Operators The Switch Statment switch (expr) { tmp = expr; Array: sequence of identical objects in memory ”Short circuit” tests save time if (tmp == 1) goto L1; int a[10]; means space for ten integers if ( a == 3 && b == 4 && c == 5 ) { ..