C notes
Published: (February 13, 2026 at 05:59 AM EST)
8 min read
Source: Dev.to
Source: Dev.to
// == Preprocessor directives for include and macro definitions ====================
// (The lines below demonstrate how to include headers and define macros)
#include "math_utils.h" // custom header (must exist)
#include // for portable int64 types
#include
#include // use this instead of primitive built‑in types
#include
#include
#include // for time()
// -----------------------------------------------------------------------------
// Type aliases (typedefs)
// -----------------------------------------------------------------------------
// Signed integers
typedef int8_t i8;
typedef int16_t i16;
typedef int32_t i32;
typedef int64_t i64;
// Unsigned integers
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
// Floating‑point numbers
typedef float f32;
typedef double f64;
// Boolean types (using integer aliases)
typedef i8 b8;
typedef i32 b32;
// -----------------------------------------------------------------------------
// Structures and typedefs
// -----------------------------------------------------------------------------
struct vec2f {
f32 x;
f64 y;
};
typedef struct {
i8 a;
i16 b;
} vec1f;
// -----------------------------------------------------------------------------
// Enumerations
// -----------------------------------------------------------------------------
typedef enum { TYPE_INT, TYPE_FLOAT } Type;
typedef enum {
POSITION_LOW = 0,
POSITION_MID = 1,
POSITION_HIGH = 2
} Position;
// -----------------------------------------------------------------------------
// Variant type (enum + union)
// -----------------------------------------------------------------------------
typedef struct {
Type type;
union {
int i;
float f;
} value;
} Variant;
// -----------------------------------------------------------------------------
// Function prototype (C contract)
// -----------------------------------------------------------------------------
void greet(void);
// -----------------------------------------------------------------------------
// Macro definitions
// -----------------------------------------------------------------------------
#define PI 3.14
#define MAX(x, y) ((x) > (y) ? (x) : (y))
// -----------------------------------------------------------------------------
// Union example
// -----------------------------------------------------------------------------
union Data {
int i;
float f;
};
// ===========================================================================
// Main program
// ===========================================================================
int main(void) {
// -------------------------------------------------------------------------
// Primitive variables
// -------------------------------------------------------------------------
int my_age = 19;
char grade = 'A';
i32 age = 90;
i64 weight = 909;
// -------------------------------------------------------------------------
// Using macros
// -------------------------------------------------------------------------
f32 actual_age = PI;
printf("Using Macros: %f\n", actual_age);
i32 num1 = 6;
i32 num2 = 8;
printf("max of num1 and num2 is: %d\n", MAX(num1, num2));
bool is_tall = true;
printf("tall: %d\n", is_tall);
b8 is_short = true;
printf("short: %d\n", is_short);
// -------------------------------------------------------------------------
// Enums
// -------------------------------------------------------------------------
printf("\n--- Enums ---\n");
Position pos = POSITION_HIGH;
if (pos == POSITION_HIGH) {
printf("Position is HIGH (%d)\n", pos);
} else if (pos == POSITION_MID) {
printf("Position is MID (%d)\n", pos);
} else if (pos == POSITION_LOW) {
printf("Position is LOW (%d)\n", pos);
}
// -------------------------------------------------------------------------
// Random numbers (stdlib + time)
// -------------------------------------------------------------------------
srand((unsigned int)time(NULL)); // seed once
int random_number = rand(); // 0 … RAND_MAX
i32 random_0_9 = rand() % 10; // 0 … 9
i32 random_1_100 = (rand() % 100) + 1; // 1 … 100
printf("Random number (0 - RAND_MAX): %d\n", random_number);
printf("Random number (0 - 9): %d\n", random_0_9);
printf("Random number (1 - 100): %d\n", random_1_100);
// -------------------------------------------------------------------------
// Structures
// -------------------------------------------------------------------------
struct vec2f v;
v.x = 18.0f;
v.y = 19.0;
// Struct with typedef – designated initializer
vec1f v1 = {13, 14};
printf("v1 vector is %d, %d \n", v1.a, v1.b);
printf("Size of struct: %zu bytes\n", sizeof(v1));
vec1f v3 = {
.a = 89, // if a is not set it becomes zero automatically
.b = 89 // positional arguments
};
printf("Vector v3 = \n", v3.a, v3.b);
// Compound literal (NOT a cast)
v3 = (vec1f){ .a = 9, .b = 10 };
printf("Vector v3 after compound literal = \n", v3.a, v3.b);
// Print struct members individually
printf("vector is (%.2f, %.2lf)\n", v.x, v.y);
printf("Vector v1 = \n", v1.a, v1.b);
printf("age is: %d\n", age);
printf("Weight is: %" PRId64 "\n", weight);
// PRId64 expands to either "lld" or "ld" depending on the platform
// -------------------------------------------------------------------------
// Unions
// -------------------------------------------------------------------------
union Data d;
d.i = 10;
printf("d.i = %d\n", d.i);
d.f = 3.14f; // overwrites same memory
printf("d.f = %f\n", d.f);
printf("Size of union: %zu bytes\n", sizeof(d));
// -------------------------------------------------------------------------
// Variant (enum + union)
// -------------------------------------------------------------------------
Variant va;
va.type = TYPE_INT;
va.value.i = 42;
if (va.type == TYPE_INT) {
printf("int = %d\n", va.value.i);
}
// -------------------------------------------------------------------------
// Pointers
// -------------------------------------------------------------------------
i32 xx = 89; // variable stored in RAM
i32 *pxx = &xx; // pointer to xx
printf("memory address of xx is %p and the real value is %d\n", (void *)pxx, xx);
*pxx = 130; // modify the value via the pointer
printf("after change – address %p, value %d\n", (void *)pxx, xx);
// Pointer to struct
vec1f v4 = {.a = 19, .b = 23};
vec1f *pv4 = &v4;
printf("initial vector before pointer %d %d\n", v4.a, v4.b);
// Method 1 – explicit dereference
(*pv4).a = 20;
(*pv4).b = 16;
printf("after explicit deref %d %d\n", v4.a, v4.b);
// Method 2 – arrow operator
pv4->a = 25;
pv4->b = 16;
printf("after arrow operator %d %d\n", v4.a, v4.b);
// -------------------------------------------------------------------------
// Arrays
// -------------------------------------------------------------------------
i32 numbers[] = {1, 2, 3, 4};
printf("index 0 is: %d\n", numbers[0]);
// Loop through the array
for (i32 i = 0; i < 3; ++i) {
printf("%d ", numbers[i]);
}
printf("\n");
// Array name decays to pointer to first element
*numbers = 6; // numbers[0] = 6
*(numbers + 1) = 90; // numbers[1] = 90
for (i32 i = 0; i < (i32)(sizeof(numbers) / sizeof(numbers[0])); ++i) {
printf("%d ", numbers[i]);
}
printf("\n");
// -------------------------------------------------------------------------
// Dynamic memory (malloc)
// -------------------------------------------------------------------------
printf("\n--- Dynamic Memory (malloc) ---\n");
i32 count = 4;
// Allocate memory for 4 i32 values
i32 *dyn_array = (i32 *)malloc(count * sizeof(i32));
if (!dyn_array) {
perror("malloc failed");
return EXIT_FAILURE;
}
// Initialise and print
for (i32 i = 0; i < count; ++i) {
dyn_array[i] = i * 10;
printf("%d ", dyn_array[i]);
}
printf("\n");
// Clean up
free(dyn_array);
return 0;
}
Elements
i32 *dyn_numbers = malloc(count * sizeof(i32));
/* If i32 = 4 bytes and count = 4
malloc(4 * 4) = malloc(16 bytes) */
if (dyn_numbers == NULL) {
printf("Memory allocation failed!\n");
return 1;
}
/* assign values */
for (i32 i = 0; i < count; i++) {
dyn_numbers[i] = (i + 1) * 10;
}
/* print values */
for (i32 i = 0; i < count; i++) {
printf("%d ", dyn_numbers[i]);
}
printf("\n");
/* always free heap memory */
free(dyn_numbers);
dyn_numbers = NULL; /* good practice */
Using calloc (zero‑initialized)
printf("\n--- Dynamic Memory (calloc) ---\n");
i32 *zero_numbers = calloc(4, sizeof(i32));
if (!zero_numbers) {
printf("Calloc failed!\n");
return 1; /* or return EXIT_FAILURE */
}
for (i32 i = 0; i < 4; i++) {
printf("%d ", zero_numbers[i]); /* prints 0 0 0 0 */
}
printf("\n");
free(zero_numbers);
zero_numbers = NULL;
realloc (resize memory)
printf("\n--- Realloc Example ---\n");
i32 *resize_array = malloc(2 * sizeof(i32));
resize_array[0] = 1;
resize_array[1] = 2;
/* increase size to 4 elements */
resize_array = realloc(resize_array, 4 * sizeof(i32));
resize_array[2] = 3;
resize_array[3] = 4;
for (i32 i = 0; i < 4; i++) {
printf("%d ", resize_array[i]);
}
printf("\n");
free(resize_array);
resize_array = NULL;
Functions
/* ---------------- Functions ---------------- */
greet();
/* import files from different source */
i32 res = add(2, 5);
printf("Imported math utility function result: %d\n", res);
/* math_utils.h
#ifndef MATH_UTILS_H // Include guard (prevents double inclusion)
#define MATH_UTILS_H
// Function declarations (prototypes)
int add(int a, int b);
int multiply(int a, int b);
#endif
*/
/* math_utils.c
#include "math_utils.h"
// Function definitions
int add(int a, int b) { return a + b; }
int multiply(int a, int b) { return a * b; }
*/
/* compile & run
gcc main.c math_utils.c -o program && ./program
*/
return 0;
}
/* C processes files from top to bottom; you need a contract to tell C this */
void greet() { printf("hello there"); }
/* compile with sanitizers and warnings
gcc -Wall -Wextra -g -fsanitize=address -o main main.c && ./main
*/
/* What each flag does:
- **-Wall** enable common warnings
- **-Wextra** enable extra warnings
- **-g** include debug symbols
- **-fsanitize=address** enable AddressSanitizer (useful for learning and debugging)
Example with stricter settings:
gcc -Wall -Wextra -Werror -O0 -g -fsanitize=address -o main main.c
*/
Format Specifiers
| Specifier | Type |
|---|---|
%d | int |
%p | pointer |
%f | float |
%lf | double |
%lld | long long |
Compilation Examples
# basic compile & run
gcc main.c math_utils.c -o program && ./program
# with warnings and sanitizers
gcc main.c math_utils.c -Wall -Wextra -pedantic -o program && ./program
struct vs union
struct
- Memory layout – each member has its own independent memory location; members are stored contiguously (with possible padding for alignment).
- Size – sum of the sizes of all members plus any padding.
- Usage – all members can be accessed simultaneously; ideal for records where every field is needed (e.g., a student record, an employee, a 2‑D point).
union
- Memory layout – all members share the same starting address; only one member holds a valid value at any time.
- Size – equal to the size of its largest member.
- Usage – saves memory when a variable can hold one of several types but never more than one at once (common in embedded systems). Writing to one member overwrites any previously stored value in another member.