<!-- These are here to make sure that the code on the slide deck doesn't have to scroll !--> <style> .reveal pre code { max-height: 100% !important; padding-top: 1.5em !important; padding-bottom: 1.5em !important; } .reveal pre { width: 100% !important; height: 100% !important; } .slides { height: auto !important; width: 90% !important; max-height: 90% !important; } </style> # Structs in C Josiah Hilden --- Short Note: In the following code I use a macro I wrote ```c ASSERT(expression, function, error_message) ``` to test preconditions for functions that we cannot be expected to handle. If expression does not evaluate to true it prints `[function]: error_message` and aborts the program with `-1`. Some examples use ```c ASSERT_TRUE(expr); /* and */ ASSERT_FALSE(expr); ``` which do exit if expr is not true or false, respectively. --- ```c= /* Represents a rational number */ typedef struct { int n; // Numerator unsigned int d; // Denominator (invariant: must not be 0) } Rational; ``` --- #### Working with structs --- ```c= Rational r; r.n = 3; r.d = 2; // Copys the value of r into r2 Rational r2 = r; r.n = 5; // The value was actually copied so this will pass ASSERT_TRUE( r2.n - r.n == -2, ); ``` --- ```c= Rational r; r.n = -2; r.d = 2; Rational *r2 = &r; Rational *r3 = &r; /* Note that we have to use the arrow operator here because * this is behind a reference now. */ r2->n = 0; /* We could also do this */ (*r2).n = 3; /* This assert passes (the expression evaluates to false), * why is that? */ ASSERT_FALSE( r2->n + r3->n == 1, ); ``` --- ```c= Rational r; r.n = -2; r.d = 2; Rational *r2 = &r; Rational *r3 = &r; /* Note that we have to use the arrow operator here because * this is behind a reference now. */ r2->n = 0; /* We could also do this */ (*r2).n = 3; /* They both alias the same underlying memory (r) so the * asignment of 3 applied to r, r2, and r3 simultaniously */ ASSERT_TRUE( r2->n + r3->n == 6, ); ``` --- #### Better methods for creating and working with structs --- ```c= /* Set the values of a Rational * Returns -1 if d is 0, otherwise 0 */ int set_rational(Rational *rat, int n, unsigned int d) { if (d == 0) return -1; rat->n = n; rat->d = d; return 0; } ``` --- ```c= /* Create a new stack allocated rational number, * Aborts program if d is 0 */ Rational new_rational(int n, unsigned int d) { Rational rat; // Initalize fields ASSERT( // set_rational returns 0 if it worked set_rational(&rat, n, d) == 0, "new_rational", "The denominator of a rational may not be 0" ); // Hand over to caller stack return rat; } ``` --- ```c= /* Create a new heap allocated rational number, * Returns null if d is 0 */ Rational *new_boxed_rational(int n, unsigned int d) { // Create a Rational on the heap Rational *rat = (Rational*)malloc(sizeof(Rational)); // Initalize the rational and capture failure state int res = set_rational(rat, n, d); // Handle possible failure and propogate error if (res != 0) return NULL; else return rat; } ``` This function also has an error in it. What are we not accounting for? --- ```c= /* Create a new heap allocated rational number, * Returns null if d is 0 */ Rational *new_boxed_rational(int n, unsigned int d) { // Create a Rational on the heap Rational *rat = (Rational*)malloc(sizeof(Rational)); // Initalize the rational and capture failure state int res = set_rational(rat, n, d); // Handle possible failure and propogate error if (res != 0) { free(rat); return NULL; } else return rat; } ``` --- ```c= /* Deallocate a heap allocated Rational */ void free_rational(Rational *del) { ASSERT(del, "free_rational", "Cannot free null"); free(del); } /* Delete heap allocated rational and remove dangling pointer * from behind del */ void delete_rational(Rational* *del) { ASSERT(del, "dlete_rational", "Cannot delete behind null") free_rational(*del); /* Remove the pointer from the caller so that they don't * accidentally use freed memory */ *del = NULL; } ``` --- #### Printing --- ```c= /* Prints the fractional representation of the rational to * a file descriptor. Does not print newlines. * * Example usage: * print_rational(STDOUT, &rat); * * Example output: * 5 / 3 */ void print_rational(unsigned int fd, const Rational *rat) { fprintf(fd, "%d / %d", rat->r, rat->d); } ``` --- ```c= /* Print a debug view of the rational to a file descriptor. * Prints out new lines. * * Example usage * debug_rational(stderr, &rat); * * Example output * Rational { * n: 5; * d: 3; * }; */ void debug_rational(unsigned int fd, const Rational *rat) { fprintf(fd, "Rational {\n\tn: %d;\n\t%d;\n};\n", rat->r, rat->d); } ``` --- #### Copying --- ```c= /* Copy the src Rational into the Rational at dest */ void copy_rational(Rational *dest, const Rational *src) { ASSERT( src, "copy_rational", "Source Rational may not be null" ); ASSERT( dest, "copy_rational", "Destination Rational may not be null" ); set_rational(dest, src->r, src->d); } ``` --- ```c= /* Clone the src Rational onto the heap and set the dest * pointer to point to it. * src and dest may not be null. the pointer behind dest may be * null or dangling. */ void clone_rational_into(Rational* *dest, const Rational *src) { ASSERT( src, "clone_rational_into", "Source Rational may not be null" ); ASSERT( dest, "clone_rational_into", "Cannot place new rational behind null (dest)" ); *dest = new_boxed_rational(src->r, src->d); } ``` --- ```c= /* Clone the src Rational onto the heap and return address * Returns null if src rational is not in a valid state */ Rational *clone_rational(const Rational *src) { ASSERT( src, "clone_rational", "Source Rational may not be null" ); // Propgates null error from new_boxed_rational return new_boxed_rational(src->r, src->d); } ```
{"metaMigratedAt":"2023-06-15T19:58:14.629Z","metaMigratedFrom":"YAML","title":"Structs in C","breaks":false,"slideOptions":"{\"center\":false}","contributors":"[{\"id\":\"71668cc9-9438-4e0d-8b0c-11542f52da40\",\"add\":7127,\"del\":648}]"}
    183 views