Quick Contact

āœ‰
fahimkhan20148@gmail.com
šŸ“±
+971 507 286 133
Back to Notes
August 24, 2026
grillcmemorystorage-classespointersbitwise

Grill Session: C Systems Programming Knowledge Diagnostic

[!NOTE] Objective: Stress-test and map the user's C systems programming knowledge, uncover conceptual gaps, and establish a clear learning frontier. Status: Concluded (Evergreen)

Context & Problem Statement

The user is learning C and requested an interactive diagnostic grill to test retention against their vault notes (including Storage Classes, Linkage, Scope, and C17 Systems Hub). This session systematically mapped understanding across 9 technical rounds from basic linkage to function pointer jump tables.

Design Tree & Rounds Log

Round 1: Translation Unit Linkage

ā“ Q1 - Internal vs External Linkage in C: In a multi-file C project, what is the fundamental difference between defining a file-scope variable as static int counter = 0; vs int counter = 0; in helper.c, when main.c uses extern int counter;?

šŸ’¬ User Answer: "for static both object files buid fine but fail to link and for no modifier main.c can use extern modifyer and access the counter form helper c to read or update it."

šŸ“Š Evaluation & Analysis: Full Mastery (100%). The user clearly understands translation unit compilation vs linking resolution. They recognized that static limits the symbol to internal linkage (causing an undefined reference at link time), while default file-scope variables possess external linkage, enabling extern declarations to resolve and share mutable state across translation units.


Round 2: Storage Duration & Lifetimes

ā“ Q2 - Static Local Variables vs Auto Variables & Initialization: Output across 3 calls, memory segment location/lifetime, and why runtime initialization is disallowed for static variables.

šŸ’¬ User Answer: "on each execution the auto_val will start from 10 an print 11 cause auto_cal lives on stack and initilaised each time log_event is called. but static_cal will go from 10 the initial value to 11 12 13 on each call cause static variables live in data segemt are initilased before main and live till the programs lifetime. we need to init static vlaues with a compile time known valid L value"

šŸ“Š Evaluation & Analysis: Strong Mastery (95%). Spot-on breakdown of stack allocation vs Data segment (static storage duration). Correctly predicted output 11, 11, 11 for auto vs 11, 12, 13 for static. Understood that static initialization occurs at load time prior to main(), requiring a compile-time constant expression.


Round 3: Register Storage Class

ā“ Q3 - The register Storage Class & The Address Operator: Compiler hint semantics, taking the address of a register variable, and scope constraints.

šŸ’¬ User Answer: "compiler can still put the value on stack due to optimisation constaines register is more of a request then a cmmand. if x is on stack we can take its address but if x happens to be in a regisetr the program would fail cause we cant take address of a register. stander C dosnt low global variables to be in registers caus eit will hog one regiter for the enter runtime of the program"

šŸ“Š Evaluation & Analysis: Partial Mastery (60%) - Key Misconception Identified.

  • Correct: Correctly stated that register is a hint/request, not a mandate, and that file-scope globals cannot be register.
  • Misconception: Believed that &x is conditionally allowed if the compiler places the variable on the stack. In the ISO C standard, applying & to any variable declared with register is a hard compile-time constraint violation (compiler error), even if the compiler spills it to RAM/stack.

Round 4: Array Decay & sizeof Mechanics

ā“ Q4 - Array Decay, Pointers & sizeof: Value printed by sizeof in main vs function, array decay rewrite rules, and type/arithmetic differences between arr and &arr.

šŸ’¬ User Answer: "main will print 320 cause 48= 32 and 10 ints i,e 104*8 = 320, but print_szie would print the size of the pointer to array which shoudl be u_size i,e 64 maybe cause we are on a 64 bit machine. array decay is when the passed array becomes a pointer to the array insted of the array. i,e it defaults to pass by refence insted of passby value. as for 3 arry and &arry are effectivly same when passing to a function"

šŸ“Š Evaluation & Analysis: Foundational Gaps Identified (40%).

  1. sizeof units: Confused bits vs bytes. sizeof returns size in bytes. int arr[10] is 10 * 4 = 40 bytes in main(), and pointer size on 64-bit is 8 bytes (not 64).
  2. Array Decay: Decays to a pointer to the first element (int*), not a pointer to the array (int(*)[10]). Compiler rewrites void func(int arr[10]) to void func(int *arr).
  3. arr vs &arr: While both evaluate to the same starting memory address, their types and pointer arithmetic differ fundamentally: arr + 1 advances by 4 bytes (sizeof(int)), whereas &arr + 1 advances by 40 bytes (sizeof(int[10])).

Round 5: String Literals & Read-Only Memory

ā“ Q5 - Array of Chars vs Pointer to String Literal: Memory allocation, mutating stack array vs string literal in read-only memory, and const qualification.

šŸ’¬ User Answer: "str1 is on stack and str2 is a constant so it should be in the data segment. statment A is fine but Statment B withh cause error as stying to write to a constant we can use conat char to tell the compiler that its a const poinrt i.e readonly"

šŸ“Š Evaluation & Analysis: Strong Mastery (90%). Correctly identified that str1 is allocated on the stack (mutable), while "Hello" for str2 resides in read-only memory (.rodata). Correctly recognized that Statement B triggers a runtime crash/UB (segmentation fault) for attempting to write to read-only memory, and correctly identified const char * as the proper declaration to catch this error at compile time.


Round 6: Struct Layout & Alignment

ā“ Q6 - Struct Memory Layout & Padding: Memory calculation for reordered struct fields, internal vs tail padding, and why alignment is required by hardware/ABIs.

šŸ’¬ User Answer: "sampleA will have the size 8 and be have 10 cause a 4 byte int will be in a memory location that a multipal of 4 and 1 can stay in a multiple of 1 i,e address is a multiple of the size. so char a an go at spot 1 then a 2byte padding so we can put b at positon 4 and the c at postion 8; for sampleB we add a 4byte padding then b an then a and c total of 10 bytes. 3 i dont know"

šŸ“Š Evaluation & Analysis: Foundational Gaps Identified (35%).

  • Intuition: Good grasp that field offsets must be multiples of field alignment (int at multiple of 4).
  • Misunderstandings:
    1. No front-padding: Structs always place their first member at offset 0.
    2. Missing Tail Padding: Total struct size must always be a multiple of the largest member's alignment requirement (max_align = 4).
    3. Actual sizes: SampleA is 12 bytes (1 byte a + 3 pad + 4 bytes b + 1 byte c + 3 tail pad = 12). SampleB is 8 bytes (4 bytes b + 1 byte a + 1 byte c + 2 tail pad = 8).
    4. Hardware rationale: Memory bus word-aligned fetches prevent split-cycle reads or CPU bus faults. Tail padding ensures array elements struct S arr[N] maintain internal alignment across all elements.

Round 7: Bitwise Operations & Endianness

ā“ Q7 - 16-Bit Big-Endian Opcode Reconstruction & Bitmasking: Combining two 8-bit bytes into a 16-bit opcode, and extracting nibbles/addresses using bitwise operations.

šŸ’¬ User Answer: "i know that we need to use bit shift to shit the bit around and use | to combine the bits but i dont know the excat steps"

šŸ“Š Evaluation & Analysis: Conceptual Intuition / Implementation Gap Identified (40%).

  • Intuition: Knows that bit-shifting moves bit positions and | merges bitfields together.
  • Mechanics Breakdown:
    1. Combine bytes: uint16_t opcode = (mem[0] << 8) | mem[1];
    2. Top nibble: (opcode >> 12) & 0x0F (or (opcode & 0xF000) >> 12)
    3. Register X nibble: (opcode & 0x0F00) >> 8 (or (opcode >> 8) & 0x0F)
    4. 12-bit Address NNN: opcode & 0x0FFF

Round 8: Heap Allocation & Use-After-Free

ā“ Q8 - Heap Allocation, free(), and Pointer Invariants: free() semantics, Use-After-Free (UAF), and the ptr = NULL defensive idiom.

šŸ’¬ User Answer: "free marks the regon to be reused it donst zeroout the values the bug is called use after fress. 3 i dont know"

šŸ“Š Evaluation & Analysis: Strong Mastery (85%).

  • Strengths: Correctly understood heap allocator mechanics (free() marks memory available for reuse without wiping bytes) and identified the critical Use-After-Free (UAF) bug.
  • Defensive Idiom: Setting data = NULL; immediately after free(data) ensures dangling pointers cannot be reused and turns accidental dereferences into deterministic, discoverable faults.

Round 9: Function Pointers & Jump Tables (Knowledge Frontier)

ā“ Q9 - Function Pointer Signatures & Jump Table Dispatch: Syntax breakdown of typedef void (*handler_t)(uint16_t);, calling handlers by index, and jump tables vs giant switch statements.

šŸ’¬ User Answer: "idont know looks like we are a limit of my undertanding"

šŸ“Š Evaluation & Analysis: Frontier Boundary Identified (0%).

  • Syntax Breakdown: typedef void (*instruction_handler_t)(uint16_t opcode); defines a type alias instruction_handler_t representing a pointer to a function taking a uint16_t and returning void.
  • Calling syntax: dispatch_table[type](opcode);
  • Architectural value: Replaces $O(N)$ sequential branching / branch mispredictions with an $O(1)$ indirect pointer jump, essential for high-performance CPU emulation loops.

Settled Decisions & Final Knowledge Blueprint

🌟 Solid Mastery (Ready for Production)

  1. Linkage & Scope (static vs extern): Rock solid on file-scope internal linkage vs external symbol resolution.
  2. Variable Lifetimes: Clear understanding of stack duration vs data-segment lifetime across program execution.
  3. String Literals & Const: Clear understanding of .rodata read-only segment violations vs mutable stack arrays.
  4. Heap Mechanics: Good grasp of malloc/free allocator behavior and Use-After-Free invariants.

šŸŽÆ Primary Learning Targets (Next Steps for CHIP-8 Track)

  1. Bitwise Arithmetic: Practice shifting (<<, >>) and bitmasking (&, |) for endian packing and opcode unpacking (Teach/C17/Lessons/0003-bitwise-operations-and-opcode-fetching).
  2. Struct Alignment & Tail Padding: Memorize the rule: Struct size is always a multiple of the largest member's alignment.
  3. Pointers vs Array Decay: Remember sizeof returns bytes, arrays decay to T* (first element), and arr vs &arr share addresses but differ in pointer arithmetic strides.
  4. Function Pointers: Master typedef syntax for function pointers and jump-table dispatch (Teach/C17/Lessons/0004-instruction-dispatch-and-execution-engine).

Next Steps / Actions

  • Complete Lesson 0002: Safe Binary File I/O
  • Practice bitwise decoding in Lesson 0003: Bitwise Opcode Fetching
  • Build jump tables in Lesson 0004: Function Pointer Jump Tables