<!-- 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}]"}