// # == preprocessor are used for include directives or macro
// definitions#include "math_utils.h"#include <inttypes.h> // for portable int64
// printing
#include "math_utils.h" // custom header (must exist)
#include <inttypes.h>
#include <stdbool.h>
#include <stdint.h> // use this instead of the primitives inbuilt types
#include <stdio.h>
#include <stdlib.h>
#include <time.h> // for time()
// signed int
typedef int8_t i8;
typedef int16_t i16;
typedef int32_t i32;
typedef int64_t i64;
// unsigned int
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
// fractions
typedef float f32;
typedef double f64;
// bools
typedef i8 b8;
typedef i32 b32;
// struct
struct vec2f {
f32 x;
f64 y;
};
// typedef with struct
typedef struct {
i8 a;
i16 b;
} vec1f;
typedef enum { TYPE_INT, TYPE_FLOAT } Type;
typedef struct {
Type type;
union {
int i;
float f;
} value;
} Variant;
// c contract -> function prototype
void greet();
#define PI 3.14
#define MAX(x, y) (x > y ? x : y)
typedef enum { POSITION_LOW = 0, POSITION_MID = 1, POSITION_HIGH = 2 } Position;
union Data {
int i;
float f;
};
int main() {
// primitives
int my_age = 19;
char grade = 'A';
i32 age = 90;
i64 weight = 909;
// using macros
// the preprocessor will run and insert the actual value
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;
// Print boolean (C prints as integer 0 or 1)
printf("short: %d\n", is_short);
// enums
printf("\n--- Enums ---\n");
Position pos;
// assign using enum constant
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 number using the stdlib.h and time
// Seed random number generator (do this once)
srand((unsigned int)time(NULL));
// Generate random numbers
int random_number = rand(); // 0 to 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);
// struct
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));
// x (4 bytes)
// y (4 bytes)
vec1f v3 = {
.a = 89, // if a is not set it becomes zero automatically
.b = 89 // positional arguements
};
printf("Vector v3= < %d, %d > \n", v3.a, v3.b);
// Compound literal (NOT a cast)
v3 = (vec1f){.a = 9, .b = 10};
printf("Vector v3 after casting= < %d, %d > \n", v3.a, v3.b);
// Print struct members individually
printf("vector is (%.2f, %.2lf)\n", v.x, v.y);
printf("Vector v1 = < %d, %d > \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
// so when you write printf("%" PRId64 "\n", value); the
// preprocessor changes it to printf("%lld\n", value); or
// printf("%ld\n", value); PRI → Print d → signed decimal 64 →
// 64-bit
//
//
// Unions
union Data d;
d.i = 10;
printf("i = %d\n", d.i);
d.f = 3.14f; // overwrites same memory
printf("f = %f\n", d.f);
printf("Size of union: %zu bytes\n", sizeof(d));
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; // this has place in memory in the program in your physical ram
// pointer is a number that points to the address of the variable
i32 *pxx = &xx; // the * can be close to i32 like i32* or *pxx or i32 * pxx
printf("memory address of xx is %p and the real value is %d \n", pxx, xx);
// set another value to the address
*pxx = 130; // go to where pxx is in memory and override this value of 130
printf("memory address of xx is %p and the real value is %d \n", pxx, xx);
// pointer to struct
vec1f v4 = {.a = 19, .b = 23};
vec1f *pv4 = &v4;
// dereference the pointer to get the real value
printf("initial vector before pointer %d %d\n", v4.a, v4.b);
// method 1 to dereference and access the member
// explicit dereference
// arrow operator
(*pv4).a = 20;
(*pv4).b = 16;
printf("initial vector after pointer %d %d\n", v4.a, v4.b);
// method 2 to dereference and access the member
pv4->a = 25;
pv4->b = 16;
printf("initial vector after pointer using -> %d %d\n", v4.a, v4.b);
// arrays
// here we are allocating memories that we know at compile time
// stack memory
i32 numbers[] = {1, 2, 3, 4};
printf("index 0 is: %d \n", numbers[0]);
// loop through an array
for (i32 i = 0; i < 3; i++) {
printf("%d ", numbers[i]);
}
printf("\n");
// in C
// therefore: *numbers == numbers[0]
// Is exactly the same as: numbers[0] = 6;
// Now array becomes: 6 2 3 4
// array name == pointer to first element
// Array decay & pointer arithmetic
*numbers = 6;
// Pointer Arithmetic
// numbers + 1 → move 1 element forward
// Now array becomes: 6 90 3 4
*(numbers + 1) = 90;
for (i32 i = 0; i < sizeof(numbers) / sizeof(numbers[0]);
i++) { // better practice
printf("%d ", numbers[i]);
}
printf("\n");
// allocating dynamic memory using stdlib.h
// heap memory
// dynamic memory allocation (heap) using:
// malloc
// calloc
// realloc
// free
printf("\n--- Dynamic Memory (malloc) ---\n");
i32 count = 4;
// allocate memory for 4 i32 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 ---------------- */
greet();
// import files from different source
i32 res = add(2, 5);
printf("Imported math utility function result: %d\n", res);
// so in 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
// and in 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; }
// run with: 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
// function : Function Definitions
void greet() { printf("hello there"); }
// run with:
// gcc -Wall -Wextra -g -fsanitize=address -o main main.c && ./main
// What each flag does:
// -Wall → enable common warnings
// -Wextra → more warnings
// -g → include debug symbols
// -fsanitize=address → enable AddressSanitizer
//
// for learning and debugging
// gcc -Wall -Wextra -Werror -O0 -g -fsanitize=address -o main main.c
// Specifiers
// %d → int
// %p → pointer
// %f → float
// %lf → double
// %lld → long long
//
//
// gcc main.c math_utils.c -o program && ./program
// gcc main.c math_utils.c -o -Wall -Wextra -pedantic program && ./program
// struct vs union
// A structure creates a single, cohesive unit where each member has its own
// dedicated and independent memory location. Memory Allocation: Memory is
// allocated for all members, one after another, in contiguous memory.
// Size: The total size of a structure is the sum of the sizes of all its
// members, plus any potential padding the compiler adds for memory alignment.
// Usage: All members of a structure can be accessed simultaneously, making them
// ideal for data records where all properties are needed at once (e.g., student
// details, an employee record, or a point in 2D space).
//
// Union
// A union allows multiple data types to share the same memory location, meaning
// only one member can store a valid value at any given time. Memory Allocation:
// All members share the same starting address in memory. Size: The total size
// of a union is equal to the size of its largest member. Usage: They are useful
// for memory-saving, especially in memory-constrained systems (like embedded
// systems), or when you have a variable that can hold one of several types but
// never more than one at once. Writing to one member will overwrite the value
// of any other previously stored member.
For further actions, you may consider blocking this person and/or reporting abuse
Top comments (0)