Blog
Posts
  • C — Debugging with Valgrind

    Valgrind is a dynamic analysis tool that runs your program in a controlled environment and reports every memory error — leaks, invalid accesses, and use of uninitialised values — with the exact line where they happened.


    Step 1 — Compile with Debug Symbols

    Like GDB, Valgrind needs the -g flag to show source lines instead of raw addresses:

    gcc -Wall -Wextra -Werror -g main.c -o my_program

    Step 2 — Run Under Valgrind

    valgrind ./my_program

    This runs Memcheck (Valgrind’s default tool) and prints a summary at the end. For thorough leak detection use the full set of flags:

    valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes -s ./my_program
    FlagEffect
    --leak-check=fullreport every individual leaked block, not just totals
    --show-leak-kinds=allinclude definitely, indirectly, possibly lost, and still reachable
    --track-origins=yesshow where an uninitialised value was created
    -sprint a short error summary at the end

    Reading the Output

    A clean run ends with:

    LEAK SUMMARY:
       definitely lost: 0 bytes in 0 blocks
       indirectly lost: 0 bytes in 0 blocks
         possibly lost: 0 bytes in 0 blocks
       still reachable: 0 bytes in 0 blocks
            suppressed: 0 bytes in 0 blocks
    
    ERROR SUMMARY: 0 errors from 0 contexts

    Any non-zero number is a problem to fix.


    Types of Errors

    Memory Leak — definitely lost

    Memory was allocated but never freed, and no pointer to it remains:

    int *arr = malloc(10 * sizeof(int));
    // forgot to free(arr)
    LEAK SUMMARY:
       definitely lost: 40 bytes in 1 blocks

    Fix: add free(arr) before the program exits.

    Invalid Read / Write

    Accessing memory outside an allocated block — classic buffer overflow or off-by-one:

    int *arr = malloc(3 * sizeof(int));
    arr[3] = 99;   // index 3 is out of bounds (valid: 0, 1, 2)
    Invalid write of size 4
       at 0x... main (main.c:6)
     Address 0x... is 0 bytes after a block of size 12 alloc'd

    Fix: check array sizes and loop bounds.

    Use of Uninitialised Value

    Reading a variable that was declared but never assigned:

    int x;
    if (x > 0)   // x has no defined value
        printf("positive\n");
    Conditional jump or move depends on uninitialised value(s)
       at 0x... main (main.c:5)

    With --track-origins=yes Valgrind also reports where x was allocated, making it much easier to trace the root cause.

    Invalid Free

    Freeing a pointer that was already freed, or one that was never malloc’d:

    free(ptr);
    free(ptr);   // double free
    Invalid free() / delete / delete[] / realloc()
       at 0x... free (in valgrind)
       by 0x... main (main.c:8)
     Address 0x... is 0 bytes inside a block of size 4 free'd

    Fix: set ptr = NULL immediately after free(ptr).

    Still Reachable

    Memory that was never freed but a valid pointer to it still exists at exit. Technically not a leak (the pointer is accessible), but still sloppy:

    char *buf = malloc(64);
    // buf is never freed, but main() returns with buf still in scope

    These show up under still reachable in the summary. Free everything before returning from main.


    A Typical Workflow

    # Compile
    gcc -Wall -Wextra -Werror -g main.c utils.c -o my_program
    
    # Run with full checks
    valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes -s ./my_program
    
    # If your program takes arguments
    valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes -s ./my_program arg1 arg2

    Read the output top to bottom:

    1. Each error block shows the type, the line, and the call stack
    2. The LEAK SUMMARY groups leaks by category
    3. The ERROR SUMMARY gives the total count

    Fix errors from the innermost call stack frame up — that is usually the root cause.


    Quick Reference

    Command / FlagEffect
    valgrind ./progrun with default Memcheck settings
    --leak-check=fullreport each leaked block individually
    --show-leak-kinds=allinclude all leak categories
    --track-origins=yesshow where uninitialised values come from
    -sprint error summary at the end
    definitely lostmalloc’d memory with no surviving pointer — must fix
    still reachablepointer exists but memory never freed — fix before exit
    Invalid read/writeout-of-bounds access
    Uninitialised valuevariable used before being assigned
    Invalid freedouble free or freeing a non-heap pointer
    Read More
    Created on July 2026
  • C — Debugging with GDB

    GDB (GNU Debugger) lets you run a C program step by step, inspect memory and variables at any point, and find exactly where and why something goes wrong.


    Step 1 — Compile with Debug Symbols

    By default GCC strips debug information from the binary. The -g flag includes it:

    gcc -Wall -Wextra -Werror -g main.c -o my_program

    Without -g, GDB can still run the program but cannot show source lines, variable names, or meaningful stack frames — just raw addresses.

    Step 2 — Launch GDB

    gdb ./my_program

    This opens the GDB prompt (gdb). The program is loaded but not yet running.

    If your program takes command-line arguments, use --args so GDB does not consume them:

    gdb --args ./my_program arg1 arg2

    Everything after the binary name is forwarded to your program when you run r. Without --args, passing arguments directly to gdb would be interpreted as GDB options.

    Step 3 — Use GDB TUI for a Visual Layout

    gdbtui launches GDB with a split-screen terminal UI — source code on top, command prompt below. It is far easier to follow than the plain prompt:

    gdbtui ./my_program
    gdbtui --args ./my_program arg1 arg2   # with arguments

    The top pane shows the source file with an arrow pointing at the current line. The bottom pane is the normal GDB command prompt.


    Core Commands

    r — Run

    Starts (or restarts) the program. Pass arguments after r if your program expects them:

    (gdb) r
    (gdb) r arg1 arg2

    The program runs until it hits a breakpoint, crashes, or finishes.

    b — Breakpoint

    Pauses execution before a specific line or function:

    (gdb) b 42          # break at line 42 of the current file
    (gdb) b main.c:15   # break at line 15 of main.c
    (gdb) b my_function # break at the entry of my_function

    List all breakpoints:

    (gdb) info b

    Delete a breakpoint by its number:

    (gdb) delete 1

    n — Next (step over)

    Executes the current line and moves to the next one. If the current line is a function call, the entire function runs without stepping into it:

    (gdb) n

    s — Step (step into)

    Like n, but if the current line calls a function, GDB steps inside that function:

    (gdb) s

    Use s when you want to follow execution into a function you wrote. Use n when you want to skip over library calls or functions you trust.

    c — Continue

    Resumes execution at full speed until the next breakpoint (or the end of the program):

    (gdb) c

    Evaluates and prints any expression — variable, pointer, arithmetic:

    (gdb) print x
    (gdb) print *ptr
    (gdb) print arr[2]
    (gdb) print a + b

    You can also modify a variable on the fly:

    (gdb) print x = 10

    disp — Display (auto-print on every step)

    display works like print but re-evaluates and shows the value automatically after every n, s, or c that pauses execution:

    (gdb) disp x
    (gdb) disp *ptr
    (gdb) disp arr[i]

    List active displays:

    (gdb) info display

    Remove a display by its number:

    (gdb) undisplay 1

    Useful Extra Commands

    bt — Backtrace

    Shows the full call stack at the current point — essential when diagnosing a crash:

    (gdb) bt
    #0  ft_strlen (s=0x0) at ft_strlen.c:6
    #1  ft_putstr (str=0x0) at ft_putstr.c:12
    #2  main () at main.c:20

    Read from bottom to top: main called ft_putstr which called ft_strlen where the crash occurred.

    p/x — Print in Hexadecimal

    Useful for inspecting raw memory values, addresses, and bitfields:

    (gdb) p/x flags
    (gdb) p/x *ptr

    watch — Watchpoint

    Pauses execution whenever a variable’s value changes — useful for tracking down unexpected mutations:

    (gdb) watch x

    refresh — Redraw the TUI

    The TUI pane can glitch after terminal output or resizing. refresh (or its shorthand ref) redraws it cleanly:

    (gdb) refresh
    (gdb) ref

    Run it any time the source window looks garbled.

    q — Quit

    (gdb) q

    A Typical Session

    # Compile with debug symbols
    gcc -Wall -Wextra -Werror -g main.c utils.c -o my_program
    
    # Launch with TUI (with optional arguments)
    gdbtui --args ./my_program arg1 arg2
    (gdb) b main          # break at entry
    (gdb) r               # run until breakpoint
    (gdb) disp i          # watch variable i on every step
    (gdb) disp *ptr       # watch what ptr points to
    (gdb) n               # step over next line
    (gdb) s               # step into the function call
    (gdb) print result    # inspect a value
    (gdb) c               # continue to next breakpoint
    (gdb) bt              # check the call stack after a crash
    (gdb) q               # quit

    Quick Reference

    CommandEffect
    gcc -ginclude debug symbols in the binary
    gdbtui ./proglaunch GDB with split source/command UI
    gdb --args ./prog a bpass arguments to the program, not to GDB
    rrun (or restart) the program
    b <line>set a breakpoint at a line or function
    nnext line — step over function calls
    sstep — enter function calls
    ccontinue until next breakpoint
    print <expr>evaluate and print an expression once
    disp <expr>auto-print an expression after every step
    btshow the full call stack
    p/x <expr>print value in hexadecimal
    watch <var>pause when a variable changes
    refresh / refredraw the TUI when it glitches
    qquit GDB
    Created on July 2026
  • C — Function Pointers

    A function pointer stores the address of a function in memory and lets you call it indirectly. This enables callbacks, generic utilities, and runtime behaviour selection.


    Syntax

    The declaration mirrors a normal pointer but wraps the name in parentheses and includes the return type and parameter types:

    int   (*op)(int, int);   // pointer to a function: takes two ints, returns int

    Assigning and calling:

    int add(int a, int b) { return (a + b); }
    int sub(int a, int b) { return (a - b); }
    
    int (*op)(int, int);
    
    op = &add;
    printf("%d\n", op(3, 4));   // 7
    
    op = &sub;
    printf("%d\n", op(3, 4));   // -1

    The & is optional — a function name alone already decays to its address — but writing it makes the intent explicit.

    Passing a Function Pointer to a Function

    This is the callback pattern: a function receives another function as a parameter and calls it internally.

    int apply(int a, int b, int (*f)(int, int))
    {
        return (f(a, b));
    }
    
    printf("%d\n", apply(10, 3, &add));   // 13
    printf("%d\n", apply(10, 3, &sub));   // 7

    void * — Generic Callbacks

    When the data type is not known at compile time, use void * as the parameter type. The caller casts to the right type inside the callback.

    void    apply(void *data, void (*f)(void *))
    {
        f(data);
    }

    Example callback printing an int:

    void    print_int(void *data)
    {
        printf("%d\n", *(int *)data);
    }
    
    int n = 42;
    apply(&n, print_int);   // 42

    Example callback printing a char *:

    void    print_str(void *data)
    {
        printf("%s\n", (char *)data);
    }
    
    apply("hello", print_str);   // hello

    This pattern is used extensively in linked-list functions like ft_lstiter and ft_lstmap.

    typedef for Readability

    Long function pointer types can be aliased with typedef:

    typedef void (*t_callback)(void *);
    
    void    run(void *data, t_callback f)
    {
        f(data);
    }

    Dispatch Tables

    An array of function pointers acts as a lookup table — a clean alternative to long if/else or switch chains:

    int add(int a, int b) { return (a + b); }
    int sub(int a, int b) { return (a - b); }
    int mul(int a, int b) { return (a * b); }
    
    int (*ops[3])(int, int) = {add, sub, mul};
    
    int result = ops[1](10, 4);   // calls sub → 6

    Indexed by an enum or integer, dispatch tables make it easy to extend behaviour without touching existing logic.


    Quick Reference

    SyntaxMeaning
    int (*f)(int, int)pointer to function returning int, taking two ints
    f = &addassign address of function add to f
    f(a, b)call the function through the pointer
    void (*f)(void *)generic callback — takes any pointer, returns nothing
    *(int *)datacast void * back to int * then dereference
    typedef void (*t_cb)(void *)alias a function pointer type
    Created on July 2026
  • C — Linked Lists

    A linked list is a chain of nodes where each node holds a value and a pointer to the next node. Unlike arrays, nodes are scattered in heap memory — the links are what connect them.


    [value | next] → [value | next] → [value | next] → NULL
       head                                               tail

    The Node Struct

    typedef struct s_node
    {
        int             value;
        struct s_node   *next;
    }   t_node;

    The next pointer uses struct s_node * (not t_node *) because the typedef is not yet complete at that point in the declaration.

    For a generic list (storing any type), use void * for the content:

    typedef struct s_list
    {
        void            *content;
        struct s_list   *next;
    }   t_list;

    Creating a Node

    Always allocate on the heap — stack-allocated nodes are destroyed when the function returns:

    t_node  *new_node(int value)
    {
        t_node *node;
    
        node = malloc(sizeof(t_node));
        if (!node)
            return (NULL);
        node->value = value;
        node->next  = NULL;
        return (node);
    }

    Always check the return value of malloc — if it returns NULL, allocation failed.

    Building a List

    Add to the Front

    Prepending is O(1) — no traversal needed:

    void    push_front(t_node **head, int value)
    {
        t_node *node;
    
        node = new_node(value);
        if (!node)
            return ;
        node->next = *head;
        *head = node;
    }

    head is a double pointer so the function can update the caller’s pointer.

    t_node *list = NULL;
    push_front(&list, 3);   // [3]
    push_front(&list, 2);   // [2] → [3]
    push_front(&list, 1);   // [1] → [2] → [3]

    Add to the Back

    Appending requires traversing to the last node — O(n):

    void    push_back(t_node **head, int value)
    {
        t_node *node;
        t_node *current;
    
        node = new_node(value);
        if (!node)
            return ;
        if (!*head)
        {
            *head = node;
            return ;
        }
        current = *head;
        while (current->next)
            current = current->next;
        current->next = node;
    }

    Traversing a List

    Walk from head to NULL, processing each node:

    void    print_list(t_node *head)
    {
        t_node *current;
    
        current = head;
        while (current)
        {
            printf("%d\n", current->value);
            current = current->next;
        }
    }

    Never modify head directly while traversing — use a separate current pointer.

    Searching

    Return the first node that matches a value, or NULL if not found:

    t_node  *find(t_node *head, int target)
    {
        while (head)
        {
            if (head->value == target)
                return (head);
            head = head->next;
        }
        return (NULL);
    }

    List Size

    int ft_lstsize(t_node *head)
    {
        int count;
    
        count = 0;
        while (head)
        {
            count++;
            head = head->next;
        }
        return (count);
    }

    Last Node

    t_node  *ft_lstlast(t_node *head)
    {
        if (!head)
            return (NULL);
        while (head->next)
            head = head->next;
        return (head);
    }

    Deleting a Node

    To remove a node from the middle, re-link the previous node’s next to skip it:

    void    delete_node(t_node **head, int target)
    {
        t_node *current;
        t_node *prev;
    
        current = *head;
        prev = NULL;
        while (current)
        {
            if (current->value == target)
            {
                if (prev)
                    prev->next = current->next;
                else
                    *head = current->next;   // deleting the head
                free(current);
                return ;
            }
            prev = current;
            current = current->next;
        }
    }

    Freeing the Entire List

    Walk the list and free each node. Advance the pointer before freeing the current node:

    void    free_list(t_node **head)
    {
        t_node *current;
        t_node *next;
    
        current = *head;
        while (current)
        {
            next = current->next;   // save next before freeing
            free(current);
            current = next;
        }
        *head = NULL;   // prevent dangling pointer
    }

    Never call current = current->next after free(current) — the memory is invalid.

    Applying a Function to Every Node

    Iterate over the list and call a function on each node’s content:

    void    ft_lstiter(t_list *lst, void (*f)(void *))
    {
        while (lst)
        {
            f(lst->content);
            lst = lst->next;
        }
    }

    This is the void * function pointer pattern — f can be any function that takes a void *.

    Doubly Linked Lists

    A doubly linked list adds a prev pointer so you can traverse in both directions:

    typedef struct s_dnode
    {
        int             value;
        struct s_dnode  *next;
        struct s_dnode  *prev;
    }   t_dnode;

    Insertion and deletion are more complex (two pointers to update instead of one), but moving backwards or deleting a node given only its pointer becomes O(1).


    Linked List vs Array

    ArrayLinked List
    Access by indexO(1)O(n)
    Insert at frontO(n)O(1)
    Insert at backO(1) amortisedO(n) singly / O(1) with tail pointer
    Insert in middleO(n)O(n) to find, O(1) to link
    Memorycontiguous — cache friendlyscattered — pointer overhead per node
    Resizerealloc neededjust add a node

    Use a linked list when you insert or remove frequently at the front, or when the size changes unpredictably. Use an array when you need fast indexed access.


    Quick Reference

    OperationKey point
    Create nodemalloc(sizeof(t_node)), check for NULL, set next = NULL
    Push frontO(1) — new node’s next = old head, update head
    Push backO(n) — traverse to last, set last->next = new node
    Traverseuse a current pointer, never move head
    Delete nodere-link prev->next to skip the node, then free
    Free listsave next before free, set head = NULL after
    Double pointer **headrequired when a function must update the caller’s head
    Created on July 2026
  • C — Structs

    A struct groups variables of different types under one name — useful for representing real-world objects like a point, a player, or a node in a list.


    Declaring a Struct

    struct s_point
    {
        int x;
        int y;
    };

    To use it you must write struct s_point every time, which is verbose. typedef solves this by creating an alias:

    typedef struct s_point
    {
        int x;
        int y;
    }   t_point;
    
    t_point p;
    p.x = 10;
    p.y = 20;
    printf("(%d, %d)\n", p.x, p.y);

    A common convention is s_ prefix for the struct tag and t_ prefix for the typedef name.

    Accessing Members

    Use . to access a member through a variable, and -> through a pointer:

    t_point  p;
    t_point *ptr = &p;
    
    p.x = 5;          // direct access
    ptr->x = 5;       // pointer access — equivalent to (*ptr).x

    Structs as Function Parameters

    Passing a struct by value copies the whole thing. Pass a pointer instead to avoid the copy and to allow the function to modify the original:

    void    move(t_point *p, int dx, int dy)
    {
        p->x += dx;
        p->y += dy;
    }
    
    t_point pos = {0, 0};
    move(&pos, 3, 5);
    printf("(%d, %d)\n", pos.x, pos.y);   // (3, 5)

    Nested Structs

    Structs can contain other structs:

    typedef struct s_rect
    {
        t_point top_left;
        t_point bottom_right;
    }   t_rect;
    
    t_rect r;
    r.top_left.x = 0;
    r.bottom_right.x = 100;

    Self-Referential Structs — Linked Lists

    A struct can hold a pointer to another struct of the same type. This is the foundation of linked lists:

    typedef struct s_node
    {
        int             value;
        struct s_node   *next;
    }   t_node;

    Each node points to the next one. The last node’s next is NULL.

    t_node *head = malloc(sizeof(t_node));
    head->value = 1;
    head->next  = malloc(sizeof(t_node));
    head->next->value = 2;
    head->next->next  = NULL;

    Traversing the list:

    t_node *current = head;
    while (current)
    {
        printf("%d\n", current->value);
        current = current->next;
    }

    Structs with malloc

    For dynamic allocation, use sizeof with the struct type:

    t_point *p = malloc(sizeof(t_point));
    if (!p)
        return (1);
    p->x = 10;
    p->y = 20;
    free(p);

    Quick Reference

    SyntaxMeaning
    struct s_foo { ... };declare a struct
    typedef struct s_foo { ... } t_foo;declare with alias
    p.xaccess member of a variable
    ptr->xaccess member through a pointer
    sizeof(t_foo)size of the struct in bytes
    struct s_node *nextself-referential pointer (linked list)
    Created on July 2026
  • C — Makefile

    A Makefile automates compilation. Instead of retyping gcc commands every time, you run make and the build system figures out what needs to be rebuilt.


    A Minimal Makefile

    NAME    = my_program
    CC      = gcc
    CFLAGS  = -Wall -Wextra -Werror
    SRCS    = main.c utils.c
    OBJS    = $(SRCS:.c=.o)
    
    all: $(NAME)
    
    $(NAME): $(OBJS)
    	$(CC) $(CFLAGS) $(OBJS) -o $(NAME)
    
    %.o: %.c
    	$(CC) $(CFLAGS) -c $< -o $@
    
    clean:
    	rm -f $(OBJS)
    
    fclean: clean
    	rm -f $(NAME)
    
    re: fclean all
    
    .PHONY: all clean fclean re

    How It Works

    Variables

    NAME    = my_program          # output binary name
    CC      = gcc                 # compiler
    CFLAGS  = -Wall -Wextra -Werror  # compiler flags
    SRCS    = main.c utils.c      # source files
    OBJS    = $(SRCS:.c=.o)       # replace .c with .o automatically

    $(VAR) expands a variable. $(SRCS:.c=.o) is a substitution reference — it produces main.o utils.o from main.c utils.c.

    Rules

    A rule has the form:

    target: prerequisites
    	recipe
    • target — the file to produce (or a phony label like clean)
    • prerequisites — files the target depends on; if any are newer, the recipe runs
    • recipe — shell commands to run (must be indented with a real tab, not spaces)

    Pattern Rule

    %.o: %.c
    	$(CC) $(CFLAGS) -c $< -o $@

    % is a wildcard. $< is the first prerequisite (the .c file), $@ is the target (the .o file). This single rule compiles every .c into its matching .o.

    .PHONY

    .PHONY: all clean fclean re

    Tells make these targets are not real files. Without this, if a file named clean existed, make clean would do nothing.

    Mandatory Rules

    RuleEffect
    make / make allcompile the program
    make cleanremove .o object files
    make fcleanremove .o files and the binary
    make refull rebuild from scratch (fclean then all)

    Every C project should have all four.

    Incremental Compilation

    make only recompiles files that changed. If you edit utils.c, only utils.o is rebuilt — main.o is untouched. This saves time on large projects.

    Common Mistakes

    Spaces instead of tabs — the recipe line must start with a tab character. Most editors show them the same but make will refuse:

    Makefile:10: *** missing separator. Stop.

    Forgetting fclean removes the binaryclean only removes .o files. fclean also removes $(NAME). Both are required.

    Not listing all source files — if SRCS is incomplete, some files won’t compile and you’ll get linker errors about undefined symbols.


    Quick Reference

    SyntaxMeaning
    $(VAR)expand variable
    $(SRCS:.c=.o)substitution: replace .c with .o
    $@the target of the current rule
    $<the first prerequisite
    %.o: %.cpattern rule matching any .c.o
    .PHONYdeclare targets that are not files
    -c flagcompile to .o without linking
    Created on July 2026
  • C — Header Files

    A header file (.h) declares the interface of your code — function prototypes, types, constants, and macros — so that multiple .c files can share them without duplicating declarations.


    Why Header Files

    In C, you must declare a function before calling it. When a project grows beyond one file, repeating declarations in every .c is error-prone. A header file centralises them:

    project/
    ├── main.c
    ├── math_utils.c
    └── math_utils.h

    math_utils.h declares what math_utils.c provides. main.c includes the header and gains access to those declarations without needing to know the implementation.

    What Goes in a Header

    Put in .hKeep in .c
    Function prototypesFunction bodies
    typedef / struct definitionsLocal variables
    #define constants and macrosstatic helpers
    extern variable declarationsVariable definitions

    Never put function bodies or variable definitions in a header — they would be compiled multiple times if the header is included in several files.

    A Minimal Header

    /* math_utils.h */
    #ifndef MATH_UTILS_H
    # define MATH_UTILS_H
    
    int add(int a, int b);
    int sub(int a, int b);
    int clamp(int val, int min, int max);
    
    #endif

    The three lines around the content are the include guard — explained below.

    Include Guards

    If a header is included more than once in the same translation unit (directly or through another header), the compiler would see duplicate declarations and error. An include guard prevents this:

    #ifndef MATH_UTILS_H   // if not yet defined...
    # define MATH_UTILS_H  // ...define this token...
    
    /* all your declarations */
    
    #endif                 // end of guarded block

    On the first inclusion the token is undefined, so the block is processed and the token is defined. On any subsequent inclusion the token already exists, so the entire block is skipped.

    The token name is conventionally the filename in uppercase with . replaced by _.

    #include — Angle Brackets vs Quotes

    #include <stdio.h>    // system / standard library header
    #include "my_file.h"  // your own header (searched relative to the source file first)
    SyntaxSearch path
    <header.h>system include paths (GCC’s standard library locations)
    "header.h"current directory first, then system paths

    Always use quotes for your own headers and angle brackets for standard library ones.

    Sharing Types and Constants

    Headers are the right place for types and constants that multiple files need:

    /* types.h */
    #ifndef TYPES_H
    # define TYPES_H
    
    # define BUFFER_SIZE 1024
    # define MAX_PLAYERS 4
    
    typedef struct s_player
    {
        char    name[64];
        int     score;
        int     lives;
    }   t_player;
    
    #endif

    Any .c file that includes types.h can use t_player, BUFFER_SIZE, and MAX_PLAYERS.

    Linking Headers and Sources

    The header declares; the source defines. Both must agree on the signature:

    /* math_utils.h */
    int add(int a, int b);
    /* math_utils.c */
    #include "math_utils.h"
    
    int add(int a, int b)
    {
        return (a + b);
    }
    /* main.c */
    #include <stdio.h>
    #include "math_utils.h"
    
    int main(void)
    {
        printf("%d\n", add(3, 4));   // 7
        return (0);
    }

    Compile all .c files together:

    gcc -Wall -Wextra -Werror main.c math_utils.c -o my_program

    Or let the Makefile handle it with a pattern rule.

    The extern Keyword

    To share a variable across files, declare it extern in the header and define it once in exactly one .c file:

    /* globals.h */
    extern int g_frame_count;
    /* globals.c */
    #include "globals.h"
    int g_frame_count = 0;   // one definition

    Any file that includes globals.h can read and write g_frame_count. Global variables should be used sparingly — prefer passing data through function parameters.

    Nested Includes

    Headers can include other headers. Include guards ensure there is no infinite loop or duplication regardless of the inclusion order.

    A common pattern is a single top-level header that bundles everything:

    /* my_project.h */
    #ifndef MY_PROJECT_H
    # define MY_PROJECT_H
    
    # include <stdlib.h>
    # include <unistd.h>
    # include "types.h"
    # include "math_utils.h"
    
    #endif

    Then each .c file only needs #include "my_project.h".


    Quick Reference

    ConceptKey point
    .h filedeclarations only — no function bodies
    include guard#ifndef / #define / #endif prevents double inclusion
    <header.h>system library header
    "header.h"your own header
    typedef in .hshare a type across multiple files
    extern in .hdeclare a variable defined elsewhere
    Makefilecompile all .c files together — header is included automatically
    Created on July 2026
  • C — Recursion

    A recursive function is a function that calls itself. It is an alternative to loops for problems that can be broken into smaller identical sub-problems.


    Structure of a Recursive Function

    Every recursive function needs two parts:

    • Base case — the condition that stops the recursion
    • Recursive case — the call that reduces the problem toward the base case

    Without a base case the function calls itself forever until the program crashes.

    int factorial(int n)
    {
        if (n <= 1)                    // base case
            return (1);
        return (n * factorial(n - 1)); // recursive case
    }

    The Call Stack

    Each time a function is called, the OS pushes a stack frame onto the call stack. That frame stores the function’s local variables, parameters, and return address.

    When a recursive function calls itself, a new frame is pushed on top of the previous one. They stack up until the base case is reached, then they unwind one by one as each call returns.

    factorial(4)
      └─ factorial(3)
           └─ factorial(2)
                └─ factorial(1)  ← base case, returns 1
             returns 2 × 1 = 2
        returns 3 × 2 = 6
      returns 4 × 6 = 24

    The call stack has a fixed size. Too many nested calls exhaust it — this is a stack overflow, which causes a segmentation fault.

    Practical Examples

    Countdown

    void countdown(int n)
    {
        if (n < 0)
            return ;
        printf("%d\n", n);
        countdown(n - 1);
    }

    Recursion naturally defers work until the stack unwinds:

    void print_reverse(char *str)
    {
        if (*str == '\0')
            return ;
        print_reverse(str + 1);
        write(1, str, 1);   // printed on the way back up
    }

    Sum of an Array

    int sum(int *arr, int size)
    {
        if (size == 0)
            return (0);
        return (arr[0] + sum(arr + 1, size - 1));
    }

    Recursion vs Iteration

    RecursionIteration
    Readabilityoften cleaner for tree/list problemsclearer for simple counters
    Memoryuses call stack (risk of overflow)uses a fixed loop variable
    Performancefunction call overhead per stepgenerally faster
    Norminette ruleallowedwhile only (for forbidden)

    Recursion is the natural fit for problems with a recursive structure: trees, linked lists, path-finding, and divide-and-conquer algorithms.

    Common Mistakes

    Missing base case — infinite recursion, stack overflow:

    int bad(int n)
    {
        return (n * bad(n - 1));   // never stops
    }

    Base case never reached — e.g. decrementing when input is already negative:

    int bad(int n)
    {
        if (n == 0)       // never hit if n starts negative
            return (0);
        return (bad(n - 1));
    }

    Always verify your base case covers every possible entry value.


    Quick Reference

    ConceptKey point
    Base casestops the recursion — must always be reachable
    Recursive casecalls itself with a smaller / simpler input
    Stack framememory pushed per call — holds locals and return address
    Stack overflowtoo many nested calls exhaust the call stack
    Unwindreturn values propagate back up through each frame
    Created on July 2026
  • C — Basics

    This post covers the C foundations — everything from compiling your first file to managing memory manually.


    Compilation

    C is a compiled language. You write source code (.c), then convert it to an executable using a compiler like GCC.

    gcc main.c -o my_program   # compile main.c into an executable called my_program
    ./my_program               # run it

    Common flags:

    gcc -Wall -Wextra -Werror main.c -o my_program
    FlagEffect
    -Wallenable all standard warnings
    -Wextraenable extra warnings
    -Werrortreat warnings as errors

    Code must compile with all three. No warning tolerance.

    Variables and Types

    C is statically typed — every variable has a fixed type declared at creation.

    int     age = 25;
    float   price = 9.99;
    double  pi = 3.14159265;
    char    letter = 'A';
    char    name[] = "Sam";
    TypeSizeUse
    char1 bytesingle character or small integer
    int4 byteswhole numbers
    float4 bytesdecimal numbers (less precise)
    double8 bytesdecimal numbers (more precise)

    Constants are declared with #define or const:

    #define MAX 100
    const int LIMIT = 50;

    Standard Output

    C has no print built-in. You use write (low-level) or printf (formatted):

    #include <unistd.h>
    
    write(1, "Hello\n", 6);   // file descriptor 1 = stdout
    #include <stdio.h>
    
    printf("Hello, %s! You are %d years old.\n", name, age);

    Common format specifiers:

    SpecifierType
    %dinteger
    %ffloat
    %cchar
    %sstring
    %ppointer address

    A common exercise is to rewrite printf yourself as ft_printf.

    Conditionals

    if (age >= 18)
    {
        printf("Adult\n");
    }
    else if (age >= 13)
    {
        printf("Teenager\n");
    }
    else
    {
        printf("Child\n");
    }

    Comparison and logical operators:

    OperatorMeaning
    ==equal
    !=not equal
    > <greater / less than
    >= <=greater or equal / less or equal
    &&logical AND
    ||logical OR
    !logical NOT

    Bitwise & Bit Shift Operators

    Bitwise operators work directly on the binary representation of integers, bit by bit.

    OperatorNameExampleResult
    &AND5 & 31
    |OR5 | 37
    ^XOR5 ^ 36
    ~NOT~5-6
    <<left shift1 << 38
    >>right shift8 >> 14

    How They Work

    Each integer is a sequence of bits. The operators act on each pair of bits independently:

      5  =  0101
      3  =  0011
            ----
    & AND:  0001  =  1
    | OR:   0111  =  7
    ^ XOR:  0110  =  6

    ~ flips every bit (bitwise NOT). Because of two’s complement, ~5 gives -6.

    Bit Shifts

    << shifts bits to the left (multiplies by powers of 2). >> shifts bits to the right (divides by powers of 2).

    int x = 1;        // 0001
    x = x << 1;       // 0010 = 2
    x = x << 2;       // 1000 = 8
    x = x >> 1;       // 0100 = 4

    Shifting left by n is equivalent to multiplying by 2^n, and is faster than actual multiplication:

    int n = 3;
    int result = n << 2;   // 3 * 4 = 12

    Practical Uses

    Check if a number is odd:

    if (n & 1)
        printf("odd\n");

    Set a bit (turn on bit at position i):

    flags = flags | (1 << i);

    Clear a bit (turn off bit at position i):

    flags = flags & ~(1 << i);

    Toggle a bit:

    flags = flags ^ (1 << i);

    Check if a specific bit is set:

    if (flags & (1 << i))
        printf("bit %d is set\n", i);

    This pattern is common in systems programming, graphics (color channels), game states, and flags.

    Loops

    while

    int i = 0;
    while (i < 5)
    {
        printf("%d\n", i);
        i++;
    }

    for

    for (int i = 0; i < 5; i++)
        printf("%d\n", i);

    The Norminette forbids for loops — use while instead.

    do…while

    Executes at least once before checking the condition:

    int i = 0;
    do
    {
        printf("%d\n", i);
        i++;
    } while (i < 5);

    The Norminette also forbids do…while loops.

    Functions

    Functions in C must be declared before they are used (either defined above main, or via a prototype):

    int add(int a, int b);   // prototype
    
    int main(void)
    {
        printf("%d\n", add(3, 4));
        return (0);
    }
    
    int add(int a, int b)
    {
        return (a + b);
    }

    A function that returns nothing uses void:

    void greet(char *name)
    {
        printf("Hello, %s\n", name);
    }

    Pointers

    A pointer stores the memory address of another variable, not its value.

    int  x = 42;
    int *p = &x;      // p holds the address of x
    
    printf("%d\n", x);    // 42  — the value
    printf("%p\n", p);    // 0x... — the address
    printf("%d\n", *p);   // 42  — dereference: value at address
    SyntaxMeaning
    &xaddress of x
    *pvalue at address stored in p
    int *pdeclare p as a pointer to int

    Pointers are how C passes variables by reference to functions:

    void increment(int *n)
    {
        *n = *n + 1;
    }
    
    int main(void)
    {
        int x = 5;
        increment(&x);
        printf("%d\n", x);   // 6
    }

    Arrays

    An array is a contiguous block of memory holding multiple values of the same type:

    int scores[5] = {10, 20, 30, 40, 50};
    
    printf("%d\n", scores[0]);   // 10
    printf("%d\n", scores[4]);   // 50

    The name of an array is a pointer to its first element:

    int *p = scores;   // equivalent to &scores[0]
    printf("%d\n", *p);         // 10
    printf("%d\n", *(p + 2));   // 30

    Strings

    In C, a string is an array of char ending with a null terminator '\0':

    char name[] = "Sam";
    // equivalent to: {'S', 'a', 'm', '\0'}

    Common string operations (from <string.h>):

    #include <string.h>
    
    strlen(str);           // length (not counting '\0')
    strcpy(dest, src);     // copy src into dest
    strcat(dest, src);     // append src to dest
    strcmp(s1, s2);        // compare: 0 if equal

    A common exercise is to rewrite all of these yourself as ft_strlen, ft_strcpy, etc.

    Pointer Arithmetic on Strings

    char *s = "Hello";
    
    while (*s)
    {
        write(1, s, 1);
        s++;
    }

    s++ advances the pointer one byte — the next character.

    Dynamic Memory

    Stack memory (local variables) is freed automatically. Heap memory must be managed manually with malloc and free.

    #include <stdlib.h>
    
    int *arr = malloc(5 * sizeof(int));   // allocate 5 ints on the heap
    if (!arr)
        return (1);                       // always check for NULL
    
    arr[0] = 10;
    arr[1] = 20;
    
    free(arr);                            // release the memory
    arr = NULL;                           // avoid dangling pointer

    Rules:

    • Every malloc must have a matching free
    • Never use memory after freeing it
    • Never free the same pointer twice

    2D Arrays with malloc

    char **grid = malloc(3 * sizeof(char *));
    int i = 0;
    while (i < 3)
    {
        grid[i] = malloc(4 * sizeof(char));
        i++;
    }
    // ... use grid[row][col] ...
    // free each row, then the array itself
    i = 0;
    while (i < 3)
    {
        free(grid[i]);
        i++;
    }
    free(grid);

    Norminette

    The Norminette is a strict code style checker. Key rules:

    • Functions must be 25 lines maximum
    • No more than 5 function parameters
    • No for loops (only while)
    • Variable declarations at the top of the scope only
    • 4-space tabs, specific brace placement
    norminette my_file.c   # check style compliance

    Quick Reference

    ConceptMeaning
    gcc -Wall -Wextra -Werrorcompile with full warnings
    int *p = &xpointer to variable x
    *pdereference — value at address
    mallocallocate heap memory
    freerelease heap memory
    '\0'null terminator ending a string
    norminettestyle checker
    Created on July 2026
  • Shell Tips — Commands & Bash Scripting

    The terminal is one of the most powerful tools in a developer’s toolkit. Whether you’re on Linux, macOS, or WSL on Windows, mastering a handful of Shell commands and understanding how to write Bash scripts can dramatically speed up your workflow.


    Essential Commands

    ls — List Directory Contents

    ls shows what’s in the current directory. The flags make it genuinely useful:

    ls          # basic list
    ls -l       # long format: permissions, owner, size, date
    ls -a       # show hidden files (starting with .)
    ls -la      # combine both: long format + hidden files

    The -la combo is the one you’ll use 95% of the time. It gives you the full picture of a directory including dotfiles like .zshrc, .gitignore, etc.

    cd — Change Directory

    cd /path/to/dir   # absolute path
    cd Documents      # relative path
    cd ..             # go up one level
    cd ~              # go to home directory
    cd -              # go back to previous directory

    cd - is underrated — it toggles between the last two directories you visited, great when you’re jumping between two project folders.

    cp — Copy Files or Directories

    cp file.txt backup.txt          # copy a file
    cp -r folder/ backup_folder/    # copy a directory recursively
    cp *.png ~/Pictures/            # copy all .png files to Pictures

    Always use -r (recursive) when copying directories, otherwise cp will refuse.

    mv — Move or Rename

    mv old_name.txt new_name.txt    # rename a file
    mv file.txt ~/Documents/        # move a file
    mv folder/ ../other_folder/     # move a directory

    mv does double duty: moving and renaming are the same operation. There’s no dedicated rename command in Shell.

    chmod — Change File Permissions

    Permissions control who can read, write, or execute a file. They apply to three groups: owner, group, and others.

    chmod +x script.sh      # make a file executable
    chmod 755 script.sh     # rwxr-xr-x (owner full, others read+execute)
    chmod 644 file.txt      # rw-r--r-- (owner read+write, others read only)

    There is also a symbolic mode using +, -, and = to add, remove, or set permissions, and u (user/owner), g (group), o (others), a (all) to target who:

    chmod +x script.sh      # give execute to everyone
    chmod -x script.sh      # remove execute from everyone
    chmod +r file.txt       # give read to everyone
    chmod -w file.txt       # remove write from everyone
    
    chmod u+x script.sh     # give execute to owner only
    chmod g-w file.txt      # remove write from group
    chmod o-r file.txt      # remove read from others
    chmod a+r file.txt      # give read to all (same as +r)
    
    chmod u+rwx,go+rx file  # owner full, group and others read+execute
    chmod o= file.txt       # remove all permissions from others
    SymbolMeaning
    +add permission
    -remove permission
    =set exact permissions (removes others)
    uowner (user)
    ggroup
    oothers
    aall (u+g+o)

    The numeric mode (755, 644) is the most common in practice. Each digit is a sum of three base values:

    ValuePermission
    4read (r)
    2write (w)
    1execute (x)

    Add them together to combine permissions:

    ValuePermissions
    7rwx (4+2+1)
    6rw- (4+2)
    5r-x (4+1)
    4r— (4)
    0---

    alias — Create Command Shortcuts

    alias lets you define a shortcut for any command or chain of commands:

    alias ll="ls -la"
    alias gs="git status"
    alias ..="cd .."
    alias ...="cd ../.."

    The problem: aliases defined in the terminal only last for the current session.

    Making Aliases Permanent with .zshrc

    To persist aliases across sessions, add them to your shell config file. For Zsh (default on macOS and most modern Linux distros), that’s ~/.zshrc:

    # Open .zshrc with your editor
    nano ~/.zshrc
    
    # Add your aliases at the bottom
    alias ll="ls -la"
    alias gs="git status"
    alias gp="git push"
    alias ..="cd .."
    
    # Reload the config without restarting the terminal
    source ~/.zshrc

    For Bash users the file is ~/.bashrc instead.


    Writing a Bash Script

    A Bash script is a plain text file containing a sequence of Shell commands. The first line — the shebang — tells the OS which interpreter to use.

    The Shebang Line

    #!/bin/bash

    This must be the very first line of the file. It points to the Bash binary, so the script runs with Bash regardless of what shell the user has active.

    A Practical Example

    Here’s a script that creates a timestamped backup of a directory:

    #!/bin/bash
    
    SOURCE="$HOME/Projects"
    DEST="$HOME/Backups"
    TIMESTAMP=$(date +"%Y-%m-%d_%H-%M")
    BACKUP_NAME="backup_$TIMESTAMP"
    
    mkdir -p "$DEST/$BACKUP_NAME"
    cp -r "$SOURCE/" "$DEST/$BACKUP_NAME/"
    
    echo "Backup created: $DEST/$BACKUP_NAME"

    Save it as backup.sh, then make it executable and run it:

    chmod +x backup.sh
    ./backup.sh

    Key Script Concepts

    Variables — no spaces around =:

    NAME="Sam"
    echo "Hello, $NAME"

    Conditionals:

    if [ -f "file.txt" ]; then
      echo "File exists"
    else
      echo "File not found"
    fi

    Loops:

    for file in *.txt; do
      echo "Processing $file"
    done

    Functions:

    greet() {
      echo "Hello, $1"
    }
    greet "Sam"

    Quick Reference

    CommandPurpose
    lsList directory contents
    cdChange directory
    cpCopy files or directories
    mvMove or rename files
    chmodChange file permissions
    aliasCreate a command shortcut
    source ~/.zshrcReload shell config
    #!/bin/bashShebang — declare Bash interpreter
    Created on July 2026
© 2026 Samuel Styles