I. Syntax and Semantics of Structs in C A. motivation ------------------------------------------ MOTIVATION FOR STRUCTS Recall our coding of the Rationals using arrays: typedef int *ratl; #define ELEMS 2 #define NUM 0 #define DENOM 1 ratl rmake(int num, int denom) { ratl rat = (ratl)malloc(sizeof(int[ELEMS])); rat[NUM] = num; rat[DENOM] = denom; return rat; } ratl radd(ratl x, ratl y) { return rmake(x[NUM]*y[DENOM] + y[NUM]*x[DENOM], x[DENOM]*y[DENOM]); } What's wrong with that? ------------------------------------------ What are the characteristics of our solution? B. structs 1. declarations ------------------------------------------ A SOLUTION: STRUCTS A struct (structure or record) type can store Example: struct ratl { int num; int denom; }; struct ratl r; r [--------------------------] num [ ] [--------------------------] denom [ ] [--------------------------] ------------------------------------------ 2. caveats a. declare structs at the top of a file or in a header file ------------------------------------------ DECLARE STRUCTS AT THE TOP If you declare a struct in a function, then So best to declare a struct: - at the top - in a ------------------------------------------ b. struct tags must be used with the keyword struct ------------------------------------------ MUST USE "STRUCT" BEFORE NAME struct ratl r2; // ok, legal ratl r3; // illegal! struct ratl *r4 = // ok, legal (struct ratl *)malloc(sizeof(struct ratl)); ratl *r5 = // wrong, illegal! (ratl *)malloc(sizeof(ratl)); // wrong! wrong! ------------------------------------------ Is that annoying? c. can use typedef to avoid saying "struct" so often ------------------------------------------ STRUCTS AND TYPEDEFS Can use typedef to avoid having to write "struct" each time typedef struct rational { int num; int denom; } rat; Above is equivalent to: struct rational { int num; int denom; }; typedef struct rational rat; Now can write: rat r; // ok, legal rat *rp = // ok legal (rat *)malloc(sizeof(rat)); However: struct rat r; // illegal! rat not a struct tag struct rat *rp; // also illegal! ------------------------------------------ 3. access operators . and -> ------------------------------------------ ACCESS TO FIELDS OF A STRUCT Use the dot (.) operator: struct ratl r; Examples: r.num = 3; r.denom = 4; int xm = r.num * r.denom; Use * and . or -> to access struct pointers: struct ratl *rp; Examples: (*rp).num = 3; (*rp).denom = 4; int xm = (*rp).num * (*rp).denom; equivalently: rp->num = 3; rp->denom = 4; int xm = rp->num * rp->denom; ------------------------------------------ C. semantics and pragmatics 1. access operators (. and ->) ------------------------------------------ SEMANTICS OF DOT OPERATOR With a declaration of a struct the compiler keeps an offset for each field. s.f denotes the storage at s + offset_for_f All the offset computation is done ------------------------------------------ ------------------------------------------ SEMANTICS OF -> OPERATOR OR (*p). Example: typedef struct { char first[30]; char last[30]; int ID; double pay; } employee_t; typedef employee_t *employee; employee p; Expression Alternative equivalent address computation ===================================================== p->first (*p).first *p p->last (*p).last (char *)(*p) + 30 p->ID (*p).ID (char *)(*p) + p->pay (*p).pay (char *)(*p) + ------------------------------------------ 2. assignment of structs ------------------------------------------ ASSIGNMENT OF STRUCTS In C, can assign a struct to another struct of the same type. Assigning a struct s1 = s2 copies fields, equivalent to s1.f = s2.f; s1.g = s2.g; ... for all the fields of the type of s1 and s2. ------------------------------------------ 3. parameter passing for structs ------------------------------------------ STRUCTS ARE PASSED BY VALUE A formal parameter that is a struct is stored on the runtime stack. So a struct actual argument is copied into the formal parameter's space. This is call by value. Even arrays inside structs are copied! ------------------------------------------ How are structs and arrays treated different in parameter passing? ------------------------------------------ EXAMPLE #include "tap.h" struct arr_wrap { int arr[2]; }; void arr_by_value(struct arr_wrap aw) { aw.arr[0] = 99; aw.arr[1] = 333; } int main() { struct arr_wrap wa; wa.arr[0] = 3; wa.arr[1] = 4; ok(wa.arr[0] == 3 && wa.arr[1] == 4, "wa.arr[0] == 3 && wa.arr[1] == 4"); arr_by_value(wa); printf("wa.arr[0] is %d, wa.arr[1] is %d\n", wa.arr[0], wa.arr[1]); ok(wa.arr[0] == 3 && wa.arr[1] == 4, "wa.arr[0] == 3 && wa.arr[1] == 4"); return exit_status(); } ------------------------------------------ 4. equality comparison for structs ------------------------------------------ DON'T USE == TO COMPARE STRUCTS Use a function that compares the fields. For example: bool rat_equals(struct rational r1, struct rational r2) { return r1.num * r2.denom == r2.num * r1.denom; } This kind of field by field comparison is called a "structural comparison" (of values). ------------------------------------------ ------------------------------------------ FOR YOU TO DO Consider the struct struct car { char *make; char *model; int doors; } Write a function that compares the values of two cars structurally: bool similar_cars(struct car c1, struct car c2) ------------------------------------------ D. struct types are distinct ------------------------------------------ STRUCT TYPES ARE COMPARED BY NAME Struct types with different names are considered incompatible for: - parameter passing - assignment Example: typedef struct kstruct { int len; } km; typedef struct mstruct { int len; } miles; int main() { km height, tallness; miles top, tip; height.len = 7; tallness.len = 6; top.len = 9; tip.len = 10; height = tallness; // legal top = tip; // legal height = top; // wrong! type error! return 0; } ------------------------------------------ II. Examples with Structs in C A. rationals 1. data structure ------------------------------------------ HEADER FILE FOR RATIONALS // file: rational.h #ifndef RATIONAL_H #define RATIONAL_H 1 #include /* ... extern declarations of all operations */ #endif ------------------------------------------ How should we declare the type ratl to be used in implementing the rationals? 2. coding the operations a. construction and initialization ------------------------------------------ CONSTRUCTION AND INITIALIZATION #include #include "rational.h" /* requires: denom != 0; * ensures: result is a fresh rational number whose * numerator is num and denominator is denom */ ratl rmake(int num, int denom) { ------------------------------------------ How can we return a ratl (== a pointer to a struct rational) without making a dangling pointer? b. accessing fields ------------------------------------------ ACCESSING FIELDS ("GETTERS") // ensures: result is the numerator of r int numerator(ratl r) { } // ensures: result is the denominator of r int denominator(ratl r) { } ------------------------------------------ c. transformations ------------------------------------------ TRANSFORMATION OPERATIONS These create new objects from old ones; typical for types with immutable objects. // ensures: result is the sum of x and y ratl radd(ratl x, ratl y) { } // ensures: result is the arithmetic inverse of r ratl rnegate(ratl r) { } // ensures: result is the product of x and y ratl rmult(ratl x, ratl y) { } // requires: the numerator of r is not 0 // ensures: result is the multiplicative inverse of r ratl rinverse(ratl r) { } ------------------------------------------ What's the advantage of writing the code using the getters? d. comparisons ------------------------------------------ COMPARISONS // ensures: result is true just when // r1 and r2 are mathematically equal bool requals(ratl r1, ratl r2) { } ------------------------------------------ How would you implement rleq? III. Friday Problems with C Structs and Pointer A. vectors ------------------------------------------ VECTORS: ARRAYS WITH LENGTHS Want to combine an array with its size, for: - convenience - bounds checking However, still want to be able to: - pass it efficiently to functions, - access elements in constant time ------------------------------------------ How should we do this? 1. operations ------------------------------------------ WHAT OPERATIONS TO HAVE ON VECTORS? ------------------------------------------ What functions should we build for clients? 2. tests ------------------------------------------ TESTS FOR VECTOR #include "tap.h" #include "vector.h" int main() { plan(5); vector vec5 = mkvec(0.0, 5); ok(vec_size(vec5) == 5, "vec_size(vec5) == 5"); ok(vec_get(vec5, 0) == 0.0, "vec_get(vec5, 0) == 0.0"); ok(vec_get(vec5, 2) == 0.0, "vec_get(vec5, 2) == 0.0"); ok(vec_get(vec5, 4) == 0.0, "vec_get(vec5, 4) == 0.0"); vec_set(vec5, 3, 3.14); ok(vec_get(vec5, 3) == 3.14, "vec_get(vec5, 3) == 3.14"); return exit_status(); } ------------------------------------------ 3. data structure ------------------------------------------ WHAT DATA STRUCTURE TO USE? #ifndef VECTOR_H #define VECTOR_H 1 /* ... extern decls for functions ...*/ #endif ------------------------------------------ 4. operations a. creation and initialization ------------------------------------------ CREATION AND INITIALIZATION /* requires: sz > 0 * ensures: result is a vector with the given size, * all of whose elements are initialized to val */ vector mkvec(ELEM val, int sz) { ------------------------------------------ b. size ------------------------------------------ // requires: v is allocated (and not NULL) // ensures: result is the size of v; int vec_size(vector v) { } ------------------------------------------ c. element access and assignment ------------------------------------------ ELEMENT ACCESS AND ASSIGNMENT // requires: v is allocated (and not NULL) // requires: 0 <= i && i < vec_size(v) // ensures: result is the element at index i extern ELEM vec_get(vector v, int i) { // requires: v is allocated (and not NULL) // requires: 0 <= i && i < vec_size(v) // modifies: the element of v at index i // ensures: the element at index i becomes val extern void vec_set(vector v, int i, ELEM val) { ------------------------------------------ 5. extensions How could we do bounds checking on access and assignment? Could we write a function to do the equivalent of a for loop? How could we make it so vectors could grow and shrink at runtime?