CS 214 / Types, Preprocessor ============================ recall: structs struct struct_name { type1 name1; type2 name2; }; // declares a type "struct struct_name" with two elements // the names "name1" and "name2" can be used to access the elements // of these structs struct struct_name v; v.name1 = some_value; if (v.name2 == some_value) { ... } we can think of a struct as containing all the specified elements union ----- The union is the dual to the struct union union_name { type1 name1; type2 name2; }; // declares a type "union union_name" and two accessors (name1 and name2) unlike a struct, which contains all its elements simultaneously, a union only carries one this OR that instead of this AND that union example { int i; float f; }; union example v; // contains an int OR a float v.i = 234; // now, v contains an int v.f = 0.5; // now, v contains a float it is only safe to access a union using the same accessor that set the union we can only read an int if we placed an int v.i = 1; printf("%f\n", v.f); // not defined what gets printed -> we don't get an error, because the union does not remember which accessor was used last -> this is called an "untagged" union a tagged union struct iorf { enum { isint, isfloat } tag; union { int i; float f; } content; }; a union is as large as its largest element the union has the same alignment as the element with the largest alignment unions are pretty uncommon these days once upon a time, they were used to combine variables to save space structs, again -------------- recall: the size of a struct is at least as large as the sum of its elements so how do we handle recursive types? e.g., linked lists or binary trees coming from Java, we might expect to write struct tree { int data; struct tree left; struct tree right; }; but this is impossible sizeof(struct tree) >= sizeof(int) + 2 * sizeof(struct tree) x >= 4 + 2*x remember: struct elements are directly contained within the struct to get recursion, we need to use indirect references (pointers) struct tree { int data; struct tree *left; struct tree *right; }; using typedef with structs we can write typedef struct { double x; double y; } point; // declares an anonymous struct type and creates a synonym, point doing this for recursive types is less convenient typedef struct { int data; tree *left; tree *right; } tree; // not allowed // the name "tree" is not in scope when we defined th // anonymous struct instead, we write typedef struct tree { int data; struct tree *left; struct tree *right; } tree; // creates the types "struct tree" and "tree" Headers and the Preprocessor ---------------------------- Header files are C code that only contains declarations type declarations function prototypes maybe some macro definitions -> recall: a declaration says that something will exist a definition says what it is to compile a function call, we only need to know its signature -> we can get this from a prototype (function declaration) to call, say, printf(), we need a prototype rather than write the prototype in every source file that uses printf(), we put it in a header file and include that file in our source #include // contains prototypes and type declarations // no actual implementations the #include directive will be replaced by the contents of the file before true compilation begins syntax #include #include "path/to/local/header.h" <> means to look starting in one of the "include directories" default: /usr/include, can be configured "" means to look starting at the file's directory the contents of the include file are essentially pasted into your code note: include works with any file, not just header files but it's most common to use it for headers #define name replacement text creates a macro that replaces "name" with "replacement text" in the remainder of the file #undef name disables the macro "name" for the remainder of the file things to note: these are text replacements, not C syntax #define foo 1 + foo 12 => 1 + 12 #define forever while(1) forever { stuff; } => while (1) { stuff; } replacement happens to C tokens #define foo 120 foo bar => 120 bar foobar => foobar foo+bar => 120+bar "foo" => "foo" replacement is recursive and interleaved with #include #define main not_main #include "source.c" #undef main // the contents of source.c will be included // but "main" will be replaced by "not main" macros can have parameters #define square(X) X * X square(100) => 100 * 100 square(a+b) => a+b * a+b 1/square(n) => 1/n * n // these are both probably not what we meant better definition #define square(X) ((X) * (X)) // avoids both of the problems above // but can we still see issues? square(a+b) => ((a+b) * (a+b)) square(expensive_function()) square(x++) => ((x++) * (x++)) // unordered side effects! #define foreach(X) for( ; (X); (X) = (X)->next) foreach(p) { printf("%d"\n", p->data } => for (; (p); (p) = (p)->next) { printf("%d"\n", p->data } testing macros compile with -E to see the results of the preprocessor gcc -E myfile.c => prints the preprocessed source of myfile.c Special pseudo-macros __FILE__ will be replaced by a string literal containing the file's name __LINE__ will be repalced by an int literal representing the line number conditional compilation allow us to discard parts of our source code before compilation #if OS_WINDOWS > 4 // some code #else // some other code #endif #ifdef MACRO // stuff to include if MACRO is defined #endif #ifndef MACRO // stuff to include if MACRO is not defined #endif trick: default value for a macro #ifndef DEBUG #define DEBUG 0 #endif { if (DEBUG) printf(" x = %d\n", x); } we can tell the compiler to define macros when we invoke it gcc -DDEBUG file.c this will define DEBUG as 1 when compiling file.c