┌───────────────────────┐
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
└───────────────────────┘
The C Notebook
~ CuB3y0nd
# 



# Pragma Once and Header Guards

## Pragma Once

If the same header file gets included more than once, you can end up with some n
asty errors caused by redefining things like functions or structs.

One simple solution is `#pragma once`. Adding this line to the top of a header f
ile tells the compiler to include the file only once, even if it's referenced mu
ltiple times across your program.

```c title="my_header.h"
#pragma once

struct Point {
  int x;
  int y;
  int z;
};
```

## Header Guards

Another common way to avoid multiple inclusions is with include guards, which us
e preprocessor directives like this:

```c
#ifndef MY_HEADER_H
#define MY_HEADER_H

// some cool code

#endif
```

# Structs

## Define

```c
struct Coordinate {
  int x;
  int y;
  int z;
};
```

## Initializers

Say we have a struct defined like this:

```c
struct City {
  char *name;
  int lon;
  int lat;
};
```

There are a few different ways to initialize a struct.

### Zero Initializer

```c
struct City c = {0};
```

### Positonal Initializer

```c
struct City c = {"San Francisco", -122, 37};
```

### Designated Initializer

```c
struct City c = {
  .name = "San Francisco",
  .lon = -122,
  .lat = 37
};
```

## Accessing Fields

```c
struct City c;

c.lat = 41;

printf("Latitude: %dn", c.lat);
```

# Typedef

If you give a name to the struct while using `typedef`, you'd have 2 ways refer 
to this type:

```c
typedef struct Coordinate {
  int x;
  int y;
  int z;
} cood_t;

struct Coordinate way_1;
coord_t way_2;
```

We can optionally skip giving the struct a name:

```c
typedef struct {
  int x;
  int y;
  int z;
} coord_t;

coord_t coord;
```

In this case, you'd only be able to refer the type as `coord_t`.

# Sizeof Struct

Structs are stored contiguously in memory one field after another. Take this str
uct:

```c
typedef struct {
  int x;
  int y;
  int z;
} coord_t;
```

Assuming `int` is 4 bytes, the total size of `coord_t` would be 12 bytes.

```c
typedef struct {
  char first_initial;
  int age;
  double height;
} human_t;
```

Assuming `char` is 1 byte, `int` is 4 bytes, and `double` is 8 bytes, the total 
size of `human_t` would be 16 bytes.

- Each member of a struct must be aligned to its own alignment requirement
- The overall size of a struct must be a multiple of the largest alignment requi
rement among its members
- $padding=( alignment-( address % alignment))  % alignment$

:::tip
As a rule of thumb, ordering your fields from largest to smallest will help the 
compiler minimize padding.
:::

For example:

```c
typedef struct {
  char a;
  double b;
  char c;
  char d;
  long e;
  char f;
} poorly_aligned_t;

typedef struct {
  double b;
  long e;
  char a;
  char c;
  char d;
  char f;
} better_t;
```

`poorly_aligned_t` will insert 20 paddings, total size would be 40 bytes. But `b
etter_t` only add 4 paddings, its size would be 24 bytes only.

# Pointers

A pointer is declared with an asterisk (`*`) after the type. For example, `int *
`.

To get the address of a variable so that we can store it in a pointer variable, 
we can use the address-of operator (`&`).

```c
int age = 28;
int *p_int = &age;
```

Oftentimes we have a pointer, but we want to get access to the data that it poin
ts to. Not the address itself, but the value stored at that address.

We can use an asterisk (`*`) to do it. The `*` operator dereferences a pointer.

```c
int meaning_of_life = 42;
int *pointer_to_mol = &meaning_of_life;
int value_at_pointer = *pointer_to_mol;
// value_at_pointer = 42
```

It can be a touch confusing, but remember that the asterisk symbol is used for t
wo different things:

1. Declaring a pointer type: `int *pointer_to_thing;`
2. Dereferencing a pointer value: `int value = *pointer_to_thing;` (retrieving t
he value) or `*pointer_to_thing = 20;` (modifying the value)

## Important things related to structs

- In C, structs are passed-by-value. Updating a field in the struct does not cha
nge the original struct
- To get the change to "persist", we can return the updated struct from the func
tion (a new copy) or just pass struct's pointer, and dereference the pointer to 
modify the original struct

As you know, when you have a struct, you can access the fields with the dot (`.`
) operator:

```c
coord_t point = {10, 20, 30};

printf("X: %dn", point.x);
```

However, when you're working with a pointer to a struct, you need to use the arr
ow (`->`) operator:

```c
coord_t point = {10, 20, 30};
coord_t *ptrToPoint = &point;

printf("X: %dn", ptrToPoint->x);
```

It effectively dereferences the pointer and accesses the field in one step. To b
e fair, you can also use the dereference and dot operator (`*` and `.`) to achie
ve the same result (it's just more verbose and less common):

```c
coord_t point = {10, 20, 30};
coord_t *ptrToPoint = &point;

printf("X: %dn", (*ptrToPoint).x);
```

### Order of Operations

The `.` operator has a higher precedence than the `*` operator, so parentheses a
re necessary when using `*` to dereference a pointer before accessing a member..
. which is another reason why the arrow operator is so much more common.

## Void Pointers

A `void *` "void pointer" tells the compiler that this pointer could point to an
ything. This is why void pointers are also known as a "generic pointer".

Since void pointers do not have a specific data type, they cannot be directly de
referenced or used in pointer arithmetic without casting them to another pointer
 type first.

```c
int number = 42;
void *generic_ptr = &number;

// This doesn't work
printf("Value of number: %dn", *generic_ptr);

// This works: Cast to appropriate type before dereferencing
printf("Value of number: %dn", *(int *)generic_ptr);
```

A common pattern is to store generic data in one variable, and the type of that 
data in another variable. This is useful when you need to pass data around witho
ut knowing its type at compile time.

```c
typedef enum DATA_TYPE {
  INT,
  FLOAT
} data_type_t;

void printValue(void *ptr, data_type_t type) {
  if (type == INT) {
    printf("Value: %dn", *(int *)ptr);
  } else if (type == FLOAT) {
    printf("Value: %fn", *(float *)ptr);
  }
}

int number = 42;
printValue(&number, INT);

float decimal = 3.14;
printValue(&decimal, FLOAT);
```

# Arrays

## Declaration

```c
int numbers[5] = {1, 2, 3, 4, 5};
```

## Array as Pointer

```c
int *way_1 = numbers;
int *way_2 = &numbers[0];
```

## Accessing Elements via Indexing

```c
// Access the third element (index 2)
int way_1 = numbers[2];
int way_2 = *(numbers + 2);
```

## Array Casting

```c
#include <stdio.h>

typedef struct {
  int x;
  int y;
  int z;
} coord_t;

int main() {
  coord_t arr[3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};

  for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
      printf("%dn", *(int *)&arr[i] + j);
    }
  }

  return 0;
}
```

Because arrays are basically just pointers, and we know that structs are contigu
ous in memory, we can cast the array of structs to an array of integers:

```c
#include <stdio.h>

typedef struct {
  int x;
  int y;
  int z;
} coord_t;

int main() {
  coord_t arr[3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
  int *ptr = (int *)arr;

  for (int i = 0; i < 9; i++) {
    printf("%dn", ptr[i]);
    // printf("%dn", *(int *)&arr + i);
  }

  return 0;
}
```

## Array Decay to Pointers

So we know that arrays are like pointers, but they're not exactly the same. Arra
ys allocate memory for all their elements, whereas pointers just hold the addres
s of a memory location. In many contexts, arrays decay to pointers, meaning the 
array name becomes "just" a pointer to the first element of the array.

### When Arrays Decay

Arrays decay when used in expressions containing pointers:

```c
int arr[5];
int *ptr = arr;          // 'arr' decays to 'int *'
int value = *(arr + 2);  // 'arr' decays to 'int *'
```

And also when they're passed to functions... so they actually decay quite often 
in practice. That's why you can't pass an array to a function by value like you 
do with a struct; instead, the array name decays to a pointer.

### When Arrays Don't Decay

- `sizeof` **Operator**: Returns the size of the entire array (e.g., `sizeof(arr
)`), not just the size of a pointer
- `&` **Operator**: Taking the address of an array with `&arr` gives you a point
er to the whole array, not just the first element. The type of `&arr` is a point
er to the array type, e.g., `int (*)[5]` for an int array with 5 elements
- `Initialization`: When an array is declared and initialized, it is fully alloc
ated in memory and does not decay to a pointer

# Enumerations

You can define a new enum type like this:

```c
typedef enum DaysOfWeek {
  MONDAY,
  TUESDAY,
  WEDNESDAY,
  THURSDAY,
  FRIDAY,
  SATURDAY,
  SUNDAY,
} days_of_week_t;
```

The `typedef` and its alias `days_of_week_t` are optional, but like with structs
, they make the enum easier to use.

In the example above, `days_of_week_t` is a new type that can only have one of t
he values defined in the `enum`:

- `MONDAY`, which is 0
- `TUESDAY`, which is 1
- `WEDNESDAY`, which is 2
- `THURSDAY`, which is 3
- `FRIDAY`, which is 4
- `SATURDAY`, which is 5
- `SUNDAY`, which is 6

An enum is not a collection type like a struct or an array. It's just a list of 
integers constrained to a new type, where each is given an explicit name.

You can use the enum type like this:

```c
typedef struct Event {
  char *title;
  days_of_week_t day;
} event_t;

typedef struct Event {
  char *title;
  enum DaysOfWeek day;
} event_t;
```

## Non-Default Values

Sometimes, you want to set those enumerations to specific values. For example, y
ou might want to define a program's exit status codes:

```c
typedef enum {
  EXIT_SUCCESS = 0,
  EXIT_FAILURE = 1,
  EXIT_COMMAND_NOT_FOUND = 127,
} ExitStatus;
```

Alternatively, you can define the first value and let the compiler fill in the r
est (incrementing by 1):

```c
typedef enum {
  LANE_WPM = 200,
  PRIME_WPM, // 201
  CUBEY_WPM,  // 202
} WordsPerMinute;
```

## Switch Case

One of the best features of `enums` is that it can be used in switch statements.

- Avoid "magic numbers"
- Use descriptive names
- With modern tooling, will give you an error/warning that you haven't handled a
ll the cases in your switch

```c
switch (logLevel) {
case LOG_DEBUG:
  printf("Debug logging enabledn");
  break;
case LOG_INFO:
  printf("Info logging enabledn");
  break;
case LOG_WARN:
  printf("Warning logging enabledn");
  break;
case LOG_ERROR:
  printf("Error logging enabledn");
  break;
default:
  printf("Unknown log level: %dn", logLevel);
  break;
}
```

You'll notice that we have a `break` after each case. If you do not have a `brea
k` (or `return`), the next case will still execute: it "falls through" to the ne
xt case.

In some rare cases, you might want the fallthrough:

```c
switch (errorCode) {
case 1:
case 2:
case 3:
  printf("Minor error occurred. Please try again.n");
  break;
case 4:
case 5:
  printf("Major error occurred. Restart required.n");
  break;
default:
  printf("Unknown error.n");
  break;
}
```

## Sizeof Enum

Generally, enums in C are the same size as an `int`. However, if an enum value e
xceeds the range of an `int`, the C compiler will use a larger integer type to a
ccommodate the value, such as an `unsigned int` or a `long`.

# Union

`union` is just a combination of the two concepts with `struct` and `enum`.

```c
typedef union AgeOrName {
  int age;
  char *name;
} age_or_name_t;
```

The `age_or_name_t` type can hold either an `int` or a `char *`, but not both at
 the same time (that would be a `struct`). We provide the list of possible types
 so that the C compiler knows the maximum potential memory requirement, and can 
account for that. This is how the union is used:

```c
age_or_name_t lane = { .age = 29 };
printf("age: %dn", lane.age);
// age: 29
```

Here's where it gets interesting. What happens if we try to access the `name` fi
eld (even though we set the `age` field)?

```c
printf("name: %sn", lane.name);
```

A `union` only reserves enough space to hold the largest type in the union and t
hen all of the fields use the same memory.

Then if we try to access `.name`, we read from the same block of memory but try 
to interpret the bytes as a `char *`, which is undefined behavior. Put simply, s
etting the value of `.age` overwrites the value of `.name` and vice versa, and y
ou should only access the field that you set.

## Memory Layout

Unions store their value in the same memory location, no matter which field or t
ype is actively being used. That means that accessing any field apart from the o
ne you set is generally a bad idea.

## Union Size

A downside of unions is that the size of the union is the size of the largest fi
eld in the union. Take this example:

```c
typedef union IntOrErrMessage {
  int data;
  char err[256];
} int_or_err_message_t;
```

The `int_or_err_message_t` union will take 256 bytes whether it use `err` or not
.

## Helper Fields

One interesting (albeit not commonly used) trick is to use unions to create "hel
pers" for accessing different parts of a piece of memory. Consider the following
:

```c
typedef union Color {
  struct {
    uint8_t r;
    uint8_t g;
    uint8_t b;
    uint8_t a;
  } components;
  uint32_t rgba;
} color_t;
```

Only 4 bytes are used. And, unlike in 99% of scenarios, it makes sense to both s
et and get values from this union through both the `components` and `rgba` field
s! Both fields in the union are exactly 32 bits in size, which means that we can
 "safely" (?) access the entire set of colors through the `.rgba` field, or get 
a single color component through the `.components` field.

The convenience of additional fields, with the efficiency of a single memory loc
ation!

# Memory-Related Perils and Pitfalls

- Dereferencing bad pointers
- Reading uninitialized memory
- Overwriting memory
- Referencing nonexistent variables
- Freeing blocks multiple times
- Referencing freed blocks
- Failing to free blocks

## Operators Precedence

| Operators                                                          | Associati
vity |
| ------------------------------------------------------------------ | ---------
---- |
| `()`, `[]`, `->`, `.`                                              | left to r
ight |
| `!`, `~`, `++`, `--`, `+`, `-`, `*`, `&`, `(type)`, `sizeof`       | right to 
left |
| `*` `/`, `%`                                                       | left to r
ight |
| `+`, `-`                                                           | left to r
ight |
| `<<`, `>>`                                                         | left to r
ight |
| `<`, `<=`, `>`, `>=`                                               | left to r
ight |
| `==`, `!=`                                                         | left to r
ight |
| `&`                                                                | left to r
ight |
| `^`                                                                | left to r
ight |
| `|`                                                               | left to ri
ght |
| `&&`                                                               | left to r
ight |
| `||`                                                             | left to rig
ht |
| `?:`                                                               | right to 
left |
| `=`, `+=`, `-=`, `*=`, `/=`, `%=`, `&=`, `^=`, `|=`, `<<=`, `>>=` | right to l
eft |
| `,`                                                                | left to r
ight |

:::important
Unary `+`, `-`, and `*` have higher precedence than binary forms.
:::

## Pointer Declarations Quiz

- `int *p`: **p** is a pointer to **int**
- `int *p[13]`: **p** is an **array[13]** of pointer to **int**
- `int *(p[13])`: **p** is an **array[13]** of pointer to **int**
- `int **p`: **p** is a pointer to a pointer to an **int**
- `int (*p)[13]`: **p** is a pointer to an **array[13]** of **int**
- `int *f()`: **f** is a function returning a pointer to **int**
- `int (*f)()`: **f** is a pointer to a function returning **int**
- `int (*(*f())[13])()`: **f** is a function returning pointer to an **array[13]
** of pointers to functions returning **int**
- `int (*(*x[3])())[5]`: **x** is an **array[3]** of pointers to functions retur
ning pointers to **array[5]** of **int**s