COP 3223H meeting -*- Outline -*- * Syntax and Semantics of Structs in C ** 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? ------------------------------------------ Q: What are the characteristics of our solution? - we used macros to give names to array offsets - we used typedef to give a name to the type ratl ... - The typedef for ratl exposes the element type of the arrays (int) - The access mechanism x[DENOM] is annoying - If we want to do something similar where the types are not the same, then we can't use arrays to do that ** structs *** 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 [ ] [--------------------------] ------------------------------------------ ... multiple pieces of information of different types at the same time with constant time access to each. *** caveats **** 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 ------------------------------------------ ... its scope is limited to that function (probably not what you want) ... of a file, or ... header file **** 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! ------------------------------------------ Q: Is that annoying? Yes, a bit. It's probably historical... **** 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! ------------------------------------------ *** 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; ------------------------------------------ ** semantics and pragmatics *** 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 ------------------------------------------ ... at compile time (so is constant time) ------------------------------------------ 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) + ------------------------------------------ ... 60 ... 60 + sizeof(int) *** 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. ------------------------------------------ This is called "structural assignment". *** parameter passing for structs is similar to assignment ------------------------------------------ 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! ------------------------------------------ This can be considered a good thing or a source of inefficiency Q: How are structs and arrays treated different in parameter passing? structs are values, and so are copied array values are pointers, so those pointers are copied ------------------------------------------ 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(); } ------------------------------------------ The above is in array_by_value_in_struct.c See also struct_assign.c *** 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) ------------------------------------------ ... { return strcmp(c1.make, c2.make) == 0 && strcmp(c1.model, c2.model) == 0 && c1.doors == c2.doors; } ** struct types are distinct struct types in C are not compared structurally, like all other types in C, but are compared by name! ------------------------------------------ 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; } ------------------------------------------ See struct_typechecking.c