C# Variables, the CPU, and LLMs — From `int age = 25;` to Silicon

Published: (December 7, 2025 at 07:08 PM EST)
6 min read
Source: Dev.to

Source: Dev.to

C# Variables, the CPU, and LLMs — From int age = 25; to Silicon

Most developers “know” what a variable is:

int age = 25;
string name = "Alice";
bool isStudent = true;

But very few can answer, with scientific precision:

  • What actually happens to these variables in the compiler?
  • Where do they live: registers, stack, heap?
  • How does the JIT decide that?
  • Why does this matter for performance and for how we talk to Large Language Models (LLMs)?

In this post we’ll use a small C# example to build a systems‑level mental model of variables and then connect it to how you can ask better questions to LLMs like ChatGPT, Claude, or others.

If you want to truly understand code like a compiler engineer, and teach that understanding to an LLM so it can help you at a higher level, this is for you.

1. Mental Model: From C# Source to CPU Electrons

Here’s the core pipeline you should keep in mind every time you see a variable in C#:

  1. The C# compiler (Roslyn) translates your code into IL (Intermediate Language).
  2. The JIT compiler (at runtime) translates that IL into machine code for your CPU.
  3. The CLR runtime decides where that variable “lives”:
    • in a register (fast, inside the CPU)
    • in a stack slot (part of the call‑stack frame)
    • as a field inside an object on the heap
  4. The CPU finally operates on electrical signals in registers and memory.

🔎 The word variable only exists at the language level.
At the CPU level there are only registers, addresses, and bits.

If you want LLMs to give you answers like a systems engineer, you need to talk in terms of this pipeline, not just “variables in C#”.

2. The Example: VariablesDeepDive.cs

Imagine this file in your repository:

// File: VariablesDeepDive.cs
// Author: Cristian Sifuentes Covarrubia + ChatGPT (Deep dive into C# variables)
// Goal: Explain variables like a systems / compiler / performance engineer.

using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

partial class Program
{
    static void VariablesDeepDive()
    {
        int age = 25;
        string name = "Alice";
        bool isStudent = true;

        Console.WriteLine($"Name: {name} is {age} years old and student status is {isStudent}");

        VariablesIntro();
        ValueVsReference();
        StackAndHeapDemo();
        RefAndInParameters();
        SpanAndPerformance();
        ClosuresAndCaptures();
        VolatileAndMemoryModel();
    }

    // ------------------------------------------------------------------------
    // 1. BASIC VARIABLES – BUT WITH A LOW-LEVEL VIEW
    // ------------------------------------------------------------------------
    static void VariablesIntro()
    {
        // At C# level:
        int age = 25;
        string name = "Alice";
        bool isStudent = true;

        Console.WriteLine($"[Intro] Name: {name} is {age} years old and student status is {isStudent}");

        // WHAT ACTUALLY HAPPENS?
        //
        // C# compiler (Roslyn):
        //   - Emits IL roughly like:
        //         .locals init (
        //             [0] int32 V_0,   // age
        //             [1] string V_1,  // name
        //             [2] bool V_2)   // isStudent
        //
        // JIT compiler:
        //   - Tries to map these locals to CPU registers when possible.
        //   - Might "spill" them to the stack if registers are insufficient.
        //
        // STACK vs REGISTERS:
        //   - `int age = 25;` might never live in memory at all:
        //       the JIT can load the constant 25 directly into a register.
        //   - If the JIT needs the value across instructions and lacks registers,
        //       it stores it in a stack slot (part of the stack frame).
        //
        // STRING "Alice":
        //   - `string` is a reference type.
        //   - The reference (pointer) is stored as a local variable
        //     (likely in a register or stack slot).
        //   - The actual characters live on the managed **heap**, allocated by the runtime.
        //
        // BOOL isStudent:
        //   - In IL it's a `bool` (System.Boolean), often compiled to a single byte.
        //   - In registers it's just bits; in memory it occupies at least one byte.
    }

    // ------------------------------------------------------------------------
    // 2. VALUE TYPES vs REFERENCE TYPES (STACK vs HEAP – BUT NOT ALWAYS)
    // ------------------------------------------------------------------------
    static void ValueVsReference()
    {
        // VALUE TYPE EXAMPLE
        // ------------------
        // struct is a value type. Its data is usually stored "inline"
        // (in the stack frame, in a register, or inside another object).
        PointStruct ps = new PointStruct { X = 10, Y = 20 };

        // REFERENCE TYPE EXAMPLE
        // ----------------------
        // class is a reference type. The variable holds a *reference* (pointer)
        // to an object on the heap.
        PointClass pc = new PointClass { X = 10, Y = 20 };

        Console.WriteLine($"[ValueVsReference] Struct: ({ps.X},{ps.Y}) | Class: ({pc.X},{pc.Y})");

        // LOW LEVEL NOTES:
        //   - `PointStruct ps`:
        //       IL has a local of type PointStruct.
        //       The struct fields X, Y are part of that local’s memory.
        //       CPU can load them from a stack slot or register.
        //
        //   - `PointClass pc`:
        //       `pc` itself is a 64‑bit reference (on a 64‑bit runtime).
        //       The real data (X, Y) resides on the heap.
        //       Access pattern: load reference → follow pointer → load fields.
        //
        // PERFORMANCE IMPLICATION:
        //   - Value types avoid an extra pointer indirection and allocation,
        //     but copying them can be expensive if the struct is large.
        //   - Reference types incur a heap allocation, pointer indirection,
        //     and GC tracking, but copying is cheap (just copy the reference).
    }

    struct PointStruct
    {
        public int X;
        public int Y;
    }

    class PointClass
    {
        public int X;
        public int Y;
    }

    // ------------------------------------------------------------------------
    // 3. STACK AND HEAP DEMONSTRATION (ESCAPE ANALYSIS)
    // ------------------------------------------------------------------------
    static void StackAndHeapDemo()
    {
        // Local variable that does NOT escape the method → can stay on the stack.
        int localValue = 42;

        // Variable that escapes (captured by a lambda) → heap allocation.
        Func<int> escaped = () => localValue;

        Console.WriteLine($"[StackAndHeapDemo] escaped() = {escaped()}");
    }

    // ------------------------------------------------------------------------
    // 4. REF, IN, AND SPAN (PERFORMANCE‑ORIENTED THINKING)
    // ------------------------------------------------------------------------
    static void RefAndInParameters()
    {
        int[] numbers = { 1, 2, 3, 4, 5 };
        SumRef(ref numbers[0]);          // passes by reference, JIT may keep it in a register
        SumIn(in numbers[0]);            // read‑only reference, helps avoid copies
    }

    static void SumRef(ref int value)
    {
        value += 10;
    }

    static void SumIn(in int value)
    {
        // value is read‑only; JIT can treat it like a normal local
        int result = value + 10;
        Console.WriteLine($"[SumIn] result = {result}");
    }

    // ------------------------------------------------------------------------
    // 5. SPAN AND MEMORY‑EFFICIENCY
    // ------------------------------------------------------------------------
    static void SpanAndPerformance()
    {
        Span<int> slice = stackalloc int[3] { 10, 20, 30 };
        for (int i = 0; i < slice.Length; i++)
        {
            Console.WriteLine($"[Span] slice[{i}] = {slice[i]}");
        }
    }

    // ------------------------------------------------------------------------
    // 6. CLOSURES AND CAPTURES
    // ------------------------------------------------------------------------
    static void ClosuresAndCaptures()
    {
        int counter = 0;
        Action increment = () => counter++;
        increment();
        increment();
        Console.WriteLine($"[Closures] counter = {counter}");
    }

    // ------------------------------------------------------------------------
    // 7. VOLATILE AND THE MEMORY MODEL
    // ------------------------------------------------------------------------
    static void VolatileAndMemoryModel()
    {
        // Demonstrates the use of the volatile keyword.
        // In real multi‑core scenarios, volatile ensures reads/writes
        // are not reordered across threads.
        volatile int flag = 0;
        // ... imagine another thread sets flag = 1;
        if (flag == 1)
        {
            Console.WriteLine("[Volatile] Flag observed as 1");
        }
    }
}

Key Takeaways

  • Variables exist only in the source language; the runtime maps them to registers, stack slots, or heap locations.
  • Value types are usually stored inline; reference types store a pointer to heap‑allocated data.
  • JIT optimizations (register allocation, spilling, escape analysis) determine the actual storage location at runtime.
  • Understanding this pipeline lets you write more performant code and phrase precise questions to LLMs (e.g., “Why does the JIT spill this local to the stack?” instead of “Why is my variable slow?”).
Back to Blog

Related posts

Read more »