C++ Memory Explorer for JS Developers

Build a C++ program that reveals what JavaScript's garbage collector hides.

Introduction

30 Second Summary

When you write JavaScript, a silent system handles memory cleanup behind the scenes. You never have to think about where your variables live or when they disappear.

In this project, you will write a C++ program that makes memory visible. You will see exactly where variables are stored, allocate and free memory by hand, and understand what JavaScript's garbage collector was doing for you all along.

What You'll Build

A C++ "Memory Explorer" program that prints memory addresses, demonstrates stack vs heap allocation, and shows what happens when you forget to free memory.

By the end of this project, you'll have:

  • A fully configured C++ development environment with compiler, debugger, and VS Code integration.
  • A working program that prints memory addresses and shows where variables live (stack vs heap).
  • A concrete mental model of manual memory management mapped to explicit C++ equivalents of JavaScript's automatic cleanup.
  • Secret Mission: Add a dangling pointer scenario and an array bounds demonstration to your Memory Explorer.

Are there any prerequisites?

This project assumes familiarity with JavaScript or Node.js and basic terminal usage. You do not need any prior C++ experience.

Set Up Your C++ Development Environment

Before you can write a single line of C++, you need a compiler that translates your code into a runnable binary.

In this step, you will install the GCC compiler toolchain and the GDB debugger, set up VS Code for C++ development, and create your project workspace with build and debug configurations.

In this step, get ready to:
  • Install the C++ compiler and debugger.
  • Install the VS Code C/C++ extension.
  • Create and configure the project workspace.
Install gcc-c++ and gdb

Unlike JavaScript where Node.js interprets your code on the fly, C++ requires a compiler to transform your source files into machine code the CPU can execute directly.

  • Press the Super key to open the Activities overview.
  • Type Terminal and press Enter to open it.
  • Install the C++ compiler and debugger by running this command:
sudo dnf install gcc-c++ gdb

What does this command do?

This installs two packages from Fedora's repositories.

  • gcc-c++ provides the g++ compiler (GCC 15) that turns your .cpp files into runnable programs.
  • gdb is the GNU Debugger. It lets you pause your program, inspect variables, and step through code line by line.
  • When prompted, type y and press Enter to confirm the installation.
  • Verify the compiler installed correctly by running:
g++ --version

Check what your terminal shows:

✔️ I see version 15

GCC 15 is installed and ready. You should see something like g++ (GCC) 15.x.x in the output.

ⓧ I see an older version

You may have a previous GCC installation from an older Fedora release. Upgrade to the latest version by running:

sudo dnf upgrade gcc-c++

After the upgrade completes, run g++ --version again to confirm you see version 15.

ⓧ Command not found

The installation may have failed. Run the install command again and watch for error messages:

sudo dnf install gcc-c++

If you see a permissions error, make sure you included sudo at the start of the command. If you see a network error, check your internet connection and try again.

  • Verify the debugger installed correctly by running:
gdb --version

You should see version information for GNU gdb. The exact version number is not critical as long as the command produces output.

gdb command not found?

If gdb --version fails but g++ --version worked, run the gdb install separately:

sudo dnf install gdb

help me troubleshoot my gdb installation on Fedora 42

Install the C/C++ extension in VS Code

The C/C++ extension gives VS Code IntelliSense (code completion), syntax highlighting, and the ability to build and debug C++ programs without leaving the editor.

  • Press the Super key to open the Activities overview.
  • Type Visual Studio Code and press Enter to open it.
  • Open the Extensions panel by pressing Ctrl+Shift+X.
  • Type C++ in the search bar at the top of the Extensions panel.
  • Find the extension called C/C++ by Microsoft (it should be the first result).
  • Click Install.

After installation, you should see the Install button change to a gear icon, confirming the extension is active.

Why this extension?

The C/C++ extension auto-detects g++ from your system PATH. It uses the compiler to find header files and provide accurate IntelliSense without any manual configuration from you.

Don't see the extension?

Make sure the search bar says C++ (not C# or C alone). The publisher should say Microsoft directly below the extension name.

help me find and install the C/C++ extension in VS Code

Create and configure the project workspace

VS Code needs two configuration files to know how to build and debug your C++ programs. You will create a project folder and add these files now.

  • Switch back to your Terminal window.
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir memory-explorer
cd memory-explorer
  • Open this folder in VS Code by running:
code .

A new VS Code window opens with the memory-explorer folder as your workspace.

  • Open the integrated terminal in VS Code by pressing Ctrl+` (backtick key, usually above Tab).
  • Create the .vscode configuration folder by running:
mkdir .vscode

Now you will create the two configuration files inside this folder. First up is tasks.json, which tells VS Code how to compile your code.

  • In the VS Code Explorer sidebar (left panel), expand the .vscode folder.
  • Hover over the .vscode folder name and click the New File icon (page with a plus sign).
  • Name the file tasks.json and press Enter.
  • Paste the following configuration into tasks.json:
{
    "version": "2.0.0",
    "tasks": [
        {
            "type": "cppbuild",
            "label": "C/C++: g++ build active file",
            "command": "/usr/bin/g++",
            "args": [
                "-fdiagnostics-color=always",
                "-g",
                "-std=c++17",
                "${file}",
                "-o",
                "${fileDirname}/${fileBasenameNoExtension}"
            ],
            "options": {
                "cwd": "${fileDirname}"
            },
            "problemMatcher": [
                "$gcc"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "detail": "compiler: /usr/bin/g++"
        }
    ]
}

What does this configuration do?

  • command points to /usr/bin/g++, the compiler you just installed.
  • -g adds debug symbols so the debugger can map machine code back to your source lines.
  • -std=c++17 tells the compiler to use the C++17 standard.
  • ${file} means "whatever file is currently open in the editor." The output binary gets the same name without the extension.
  • Save tasks.json by pressing Ctrl+S.

Next, create the debug configuration that tells VS Code how to run your program with GDB attached.

  • Hover over the .vscode folder name again and click the New File icon.
  • Name the file launch.json and press Enter.
  • Paste the following configuration into launch.json:
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "C/C++: g++ build and debug active file",
            "type": "cppdbg",
            "request": "launch",
            "program": "${fileDirname}/${fileBasenameNoExtension}",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "miDebuggerPath": "/usr/bin/gdb",
            "setupCommands": [
                {
                    "description": "Enable pretty-printing for gdb",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                }
            ],
            "preLaunchTask": "C/C++: g++ build active file"
        }
    ]
}

What does this configuration do?

  • MIMode tells VS Code to use GDB as its debugger backend.
  • miDebuggerPath points to the /usr/bin/gdb binary you installed earlier.
  • preLaunchTask links to your build task. When you press F5, VS Code compiles your code first, then runs the debugger.
  • program points to the compiled binary (same name as the source file, without the extension).
  • Save launch.json by pressing Ctrl+S.
  • Confirm your files exist by checking the Explorer sidebar. You should see a .vscode folder containing both tasks.json and launch.json.

Don't see the .vscode folder in Explorer?

VS Code sometimes hides dotfiles (folders starting with a period). Click ... at the top of the Explorer panel, then make sure Show Hidden Files is enabled. You can also run ls -la in the integrated terminal to confirm the folder exists.

help me find my .vscode folder in VS Code on Linux

✔️ Awesome, I've got everything!

Great. Double check that both files are saved (no dot on the tab name means saved).

ⓧ I'd like to double check the full code

Here is the complete tasks.json:

{
    "version": "2.0.0",
    "tasks": [
        {
            "type": "cppbuild",
            "label": "C/C++: g++ build active file",
            "command": "/usr/bin/g++",
            "args": [
                "-fdiagnostics-color=always",
                "-g",
                "-std=c++17",
                "${file}",
                "-o",
                "${fileDirname}/${fileBasenameNoExtension}"
            ],
            "options": {
                "cwd": "${fileDirname}"
            },
            "problemMatcher": [
                "$gcc"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "detail": "compiler: /usr/bin/g++"
        }
    ]
}

Here is the complete launch.json:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "C/C++: g++ build and debug active file",
            "type": "cppdbg",
            "request": "launch",
            "program": "${fileDirname}/${fileBasenameNoExtension}",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "miDebuggerPath": "/usr/bin/gdb",
            "setupCommands": [
                {
                    "description": "Enable pretty-printing for gdb",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                }
            ],
            "preLaunchTask": "C/C++: g++ build active file"
        }
    ]
}

Your development environment is ready. Next up, you will write your first C++ program and see how the compilation process transforms your source code into a runnable binary.

Write and Compile Hello World

Your toolchain is installed and VS Code is configured. Now it's time to write your first C++ program and experience the compilation model firsthand.

Unlike Node.js where you run files directly with node app.js, C++ requires a separate compilation step. Your source code gets translated into a machine-code binary that runs directly on the processor.

In this step, get ready to:
  • Create a Hello World program in C++.
  • Compile it from the terminal using g++.
  • Build and run it from VS Code's integrated build task.
Create your first C++ source file

Every C++ program starts with a source file that the compiler translates into a runnable binary. Your first file will print a message to the terminal, just like console.log does in JavaScript.

  • In VS Code's Explorer sidebar, click the New File icon next to MEMORY-EXPLORER.
  • Name the file main.cpp and press Enter.
  • Paste the following code into main.cpp:
#include <iostream>

int main() {
    std::cout << "Hello from C++!" << std::endl;
    // JS equivalent: console.log("Hello from C++!");
    return 0;
}

What does this code do?

  • #include <iostream> brings in the input/output library. This is like const readline = require('readline') in Node.js.
  • int main() is the entry point of every C++ program. The operating system calls this function when you run the binary.
  • std::cout << "Hello from C++!" sends text to the terminal. Think of std::cout as C++'s version of console.log.
  • return 0; tells the operating system the program finished successfully. A non-zero return signals an error.
  • Save main.cpp with Ctrl+S.

File not appearing in the sidebar?

Make sure you created the file at the top level of the memory-explorer folder, not inside the .vscode folder. If it ended up in the wrong place, drag it to the root of the project in the Explorer sidebar.

help me move my file to the right location

Compile and run from the terminal

In Node.js, you run code with node main.js and the runtime interprets it on the fly. C++ works differently. You first compile the source into a binary, then run that binary separately.

  • Open the integrated terminal in VS Code with Ctrl+` (backtick key, top-left of keyboard).
  • Compile your program into a binary by running this command:
g++ -g -std=c++17 -o memory_explorer main.cpp

What do these flags mean?

  • -g adds debug symbols to the binary. This is what lets VS Code's debugger set breakpoints and inspect variables later.
  • -std=c++17 tells the compiler to use the C++17 standard. This matches what your tasks.json is configured to use.
  • -o memory_explorer names the output binary. Without this, the compiler would produce a file called a.out by default.
  • Run the compiled binary:
./memory_explorer
  • You should see Hello from C++! printed in the terminal.

Why the ./ prefix?

Linux does not look in the current directory for executables by default. The ./ prefix tells the shell to look in the current folder for the memory_explorer binary.

Seeing 'No such file or directory'?

Make sure your terminal is in the memory-explorer directory. Run pwd to check your current location. If you are somewhere else, run cd ~/Desktop/memory-explorer to navigate back.

Also confirm that main.cpp exists in the current directory by running ls. You should see main.cpp listed.

help me fix my compile error

Build and run from VS Code

Typing compile commands manually works, but gets tedious as your program grows. The tasks.json you created in Step 1 lets VS Code handle compilation with a single keyboard shortcut.

  • Make sure main.cpp is the active file (click its tab in the editor).
  • Press Ctrl+Shift+B to trigger the build task.

What happens when you build?

VS Code reads your tasks.json and runs the same g++ command you just ran manually. The build output appears in the terminal panel at the bottom. If the build succeeds, you will see a "Build finished successfully" message.

  • Press F5 to run your program with the debugger attached.
  • You should see Hello from C++! printed in the DEBUG CONSOLE or integrated terminal at the bottom of VS Code.

Quick build-and-run workflow

Ctrl+Shift+B compiles your code. F5 runs it with the debugger. This two-keystroke workflow is what you will use for the rest of this project.

Build task not running?

Make sure main.cpp is the active editor tab when you press Ctrl+Shift+B. The build task compiles the currently open file, so if a JSON file is active instead, it will fail.

If VS Code asks you to select a build task, choose C/C++: g++ build active file from the dropdown.

help me fix my VS Code build task

✔️ Awesome, I've got everything!

Great. Make sure you have saved main.cpp and confirmed that Hello from C++! prints in both the terminal and VS Code.

ⓧ I'd like to double check the full code

#include <iostream>

int main() {
    std::cout << "Hello from C++!" << std::endl;
    // JS equivalent: console.log("Hello from C++!");
    return 0;
}

You have a working C++ program that compiles and runs two ways. Next up, you will explore stack memory and see the actual memory addresses that JavaScript always kept hidden from you.

Explore Stack Memory

In JavaScript, when you declare a variable like let x = 42, it just appears. You never know where it lives in your computer's actual RAM.

In C++, every variable has a real, visible address in memory. In this step, you'll write functions that reveal exactly where your variables sit on the stack and how C++ destroys them the instant they leave scope.

In this step, get ready to:
  • Write a function that prints stack variable addresses.
  • Write a function that demonstrates scope-based cleanup.
  • Recompile and observe memory address patterns.
Write the stackDemo() function

The & operator in C++ returns the memory address of a variable. In JavaScript, you never see addresses. Here, you'll print them directly to the console.

  • In VS Code, open main.cpp from the Explorer sidebar.
  • Select all the existing code and delete it.
  • Add the following code to create the stackDemo() function:
#include <iostream>

// === STACK MEMORY DEMONSTRATION ===
void stackDemo() {
    std::cout << "\n=== STACK MEMORY ===" << std::endl;
    std::cout << "Stack = automatic, fast, dies when scope ends." << std::endl;
    std::cout << "In JS, you never see where variables live.\n" << std::endl;

    int x = 42;
    int y = 100;
    double z = 3.14;

    std::cout << "Variable x: value = " << x
              << ", address = " << &x << std::endl;
    std::cout << "Variable y: value = " << y
              << ", address = " << &y << std::endl;
    std::cout << "Variable z: value = " << z
              << ", address = " << &z << std::endl;

    std::cout << "\nNotice: stack addresses are close together (contiguous)." << std::endl;
    std::cout << "They typically grow downward (higher to lower address)." << std::endl;
}

What does this code do?

The &x syntax gives you the memory address where x is stored in RAM. When you send it to std::cout, it prints a hexadecimal number like 0x7ffd3a4b1c.

In JavaScript, let x = 42 hides all of this. You have no way to ask "where in RAM does x live?" The garbage collector manages addresses for you. In C++, you own that knowledge.

Why do addresses look like 0x7ffd...?

The 0x prefix means the number is in hexadecimal (base-16). Each address points to a specific byte in your computer's RAM. Your addresses will differ from anyone else's because of ASLR (Address Space Layout Randomization), a security feature that shuffles memory locations between runs.

Write the scopeDemo() function

In JavaScript, let is block-scoped, but the garbage collector decides when to actually reclaim memory. In C++, variables are destroyed the exact instant the closing brace runs.

  • Add the following scopeDemo() function below the closing brace of stackDemo():
// === SCOPE AND AUTOMATIC CLEANUP ===
void scopeDemo() {
    std::cout << "\n=== SCOPE & AUTOMATIC CLEANUP ===" << std::endl;
    std::cout << "C++ destroys variables IMMEDIATELY at the closing brace." << std::endl;
    std::cout << "JS waits for the GC to 'get around to it'.\n" << std::endl;

    int outer = 1;
    std::cout << "outer created at " << &outer << std::endl;

    {
        int inner = 2;
        std::cout << "  inner created at " << &inner << std::endl;
        std::cout << "  Both outer (" << outer << ") and inner ("
                  << inner << ") are accessible here." << std::endl;
    }

    std::cout << "inner is now DESTROYED (went out of scope)." << std::endl;
    std::cout << "In JS: let in a block is also block-scoped," << std::endl;
    std::cout << "but cleanup is delayed until GC runs." << std::endl;
    std::cout << "In C++: cleanup is IMMEDIATE at the closing brace." << std::endl;
}

What does this code do?

The nested curly braces { } create an inner scope. The variable inner is created when execution enters the block and destroyed the instant the closing brace is reached.

After the closing brace, inner no longer exists. Trying to use it would cause a compiler error. This is deterministic cleanup. You always know exactly when memory is freed.

Update main() and recompile
  • Below scopeDemo(), add a new main() function that calls both demos:
int main() {
    stackDemo();
    scopeDemo();
    return 0;
}

What does this code do?

This replaces the old Hello World main(). It calls stackDemo() to print variable addresses, then scopeDemo() to show scope-based destruction.

  • Save main.cpp.
  • Compile and run the program by running these commands in your terminal:
g++ -g -std=c++17 -o memory_explorer main.cpp
./memory_explorer

You should see output showing memory addresses for x, y, and z that are close together, followed by the scope demo showing inner being destroyed at the closing brace.

What should I see?

The stack addresses for x, y, and z will be hexadecimal numbers that are very close to each other. They typically grow downward (each subsequent variable has a slightly lower address). This shows that stack memory is contiguous.

Your exact addresses will be different from anyone else's. That is expected. The pattern (close together, decreasing) is what matters.

Seeing a compilation error?

Make sure you deleted the old main() function completely. C++ only allows one main() per program. If you see a "redefinition" error, you have two main() functions in the file.

Also check that stackDemo() and scopeDemo() appear above main() in the file. C++ reads top to bottom. If main() calls a function it has not seen yet, it will error.

Help me fix my compilation error in the Memory Explorer

✔️ Awesome, I've got everything!

Great. Double check you have saved main.cpp and your output shows addresses and scope cleanup messages.

ⓧ I'd like to double check the full code

#include <iostream>

// === STACK MEMORY DEMONSTRATION ===
void stackDemo() {
    std::cout << "\n=== STACK MEMORY ===" << std::endl;
    std::cout << "Stack = automatic, fast, dies when scope ends." << std::endl;
    std::cout << "In JS, you never see where variables live.\n" << std::endl;

    int x = 42;
    int y = 100;
    double z = 3.14;

    std::cout << "Variable x: value = " << x
              << ", address = " << &x << std::endl;
    std::cout << "Variable y: value = " << y
              << ", address = " << &y << std::endl;
    std::cout << "Variable z: value = " << z
              << ", address = " << &z << std::endl;

    std::cout << "\nNotice: stack addresses are close together (contiguous)." << std::endl;
    std::cout << "They typically grow downward (higher to lower address)." << std::endl;
}

// === SCOPE AND AUTOMATIC CLEANUP ===
void scopeDemo() {
    std::cout << "\n=== SCOPE & AUTOMATIC CLEANUP ===" << std::endl;
    std::cout << "C++ destroys variables IMMEDIATELY at the closing brace." << std::endl;
    std::cout << "JS waits for the GC to 'get around to it'.\n" << std::endl;

    int outer = 1;
    std::cout << "outer created at " << &outer << std::endl;

    {
        int inner = 2;
        std::cout << "  inner created at " << &inner << std::endl;
        std::cout << "  Both outer (" << outer << ") and inner ("
                  << inner << ") are accessible here." << std::endl;
    }

    std::cout << "inner is now DESTROYED (went out of scope)." << std::endl;
    std::cout << "In JS: let in a block is also block-scoped," << std::endl;
    std::cout << "but cleanup is delayed until GC runs." << std::endl;
    std::cout << "In C++: cleanup is IMMEDIATE at the closing brace." << std::endl;
}

int main() {
    stackDemo();
    scopeDemo();
    return 0;
}

Your Memory Explorer is already revealing things JavaScript hid from you. Those hexadecimal addresses are real locations in your computer's RAM. Next up, you'll explore the heap and learn why C++ gives you new and delete for memory that needs to outlive a function.

Explore Heap Memory

In the previous step, you saw how stack memory works. Variables are created automatically, live in contiguous addresses, and get destroyed the instant their scope ends.

Stack memory has a limitation: everything dies when the function returns. What if you need data to survive beyond the function that created it? That is exactly what heap memory solves. In this step, you will allocate memory on the heap using new, free it with delete, and see what happens when you forget to clean up.

In this step, get ready to:
  • Write a heap allocation function that shows pointer addresses and manual cleanup.
  • Demonstrate why the heap exists by creating data that outlives its function.
  • Show memory leak mechanics and how forgetting to delete causes problems.
Write the heapDemo() function

In JavaScript, every object you create lives on the heap. The garbage collector handles cleanup invisibly. In C++, you decide where data lives and you are responsible for freeing it.

  • In main.cpp, find the closing brace of scopeDemo().
  • Add the following heapDemo() function below it:
// === HEAP MEMORY DEMONSTRATION ===
void heapDemo() {
    std::cout << "\n=== HEAP MEMORY ===" << std::endl;
    std::cout << "Heap = manual, flexible, lives until YOU delete it." << std::endl;
    std::cout << "In JS, ALL objects live on the heap. GC cleans up.\n" << std::endl;

    int* heapInt = new int(99);

    std::cout << "Heap-allocated int:" << std::endl;
    std::cout << "  Value: " << *heapInt << std::endl;
    std::cout << "  Pointer address (lives on stack): " << &heapInt << std::endl;
    std::cout << "  Pointed-to address (lives on heap): " << heapInt << std::endl;

    std::cout << "\nJS equivalent:  let obj = { value: 99 };" << std::endl;
    std::cout << "C++ equivalent: int* p = new int(99);" << std::endl;
    std::cout << "Difference: YOU must call 'delete' in C++." << std::endl;

    delete heapInt;
    std::cout << "\nMemory freed with 'delete'." << std::endl;
}

What does this code do?

  • new int(99) allocates space for one integer on the heap and stores the value 99 there. It returns a pointer (a memory address) to that location.
  • &heapInt prints where the pointer variable itself lives (on the stack). heapInt without the & prints where the data lives (on the heap). Two different addresses for two different memory regions.
  • delete heapInt frees the heap memory. This is what JavaScript's garbage collector does for you automatically.
  • Save main.cpp.
  • You won't see a change yet. The next chunk wires this function into main() after all three heap functions are written.

Seeing red squiggles under new or delete?

Make sure the function is placed outside any other function (after the closing brace of scopeDemo(), not inside it). Also confirm #include <iostream> is still at the top of the file.

help me fix red squiggles in my heapDemo function

Write the heap lifetime demonstration

The real reason the heap exists is not just manual control. It is that heap data outlives the function that created it. Stack data is destroyed when the function returns. This matters in embedded systems where you need to pass data between components.

  • Below your heapDemo() function, add this helper function that creates an array on the heap:
// Helper: creates an array that outlives this function
int* createArray(int size) {
    int* arr = new int[size];
    for (int i = 0; i < size; i++) {
        arr[i] = i * 10;
    }
    return arr;
}

What does this code do?

This function allocates an array of integers on the heap using new int[size], fills each slot with i * 10, and returns the pointer. The key insight: even though createArray() finishes executing, the array survives because it lives on the heap.

In JavaScript, this is just how all arrays work. In C++, you have to choose the heap explicitly with new to get this behavior.

  • You won't see a change yet. The next function uses createArray() and prints the results.
  • Directly below createArray(), add the heapLifetimeDemo() function:
// === WHY THE HEAP EXISTS ===
void heapLifetimeDemo() {
    std::cout << "\n=== WHY THE HEAP EXISTS ===" << std::endl;
    std::cout << "Heap memory outlives the function that created it." << std::endl;
    std::cout << "Stack memory is DESTROYED when the function returns." << std::endl;
    std::cout << "In JS, ALL arrays/objects survive function returns (GC).\n"
              << std::endl;

    int* myArray = createArray(5);

    std::cout << "Array was created inside createArray() but lives on:" << std::endl;
    for (int i = 0; i < 5; i++) {
        std::cout << "  myArray[" << i << "] = " << myArray[i]
                  << " (at " << &myArray[i] << ")" << std::endl;
    }

    delete[] myArray;
    std::cout << "\nArray freed with 'delete[]'." << std::endl;
    std::cout << "Forgetting this line = MEMORY LEAK." << std::endl;
}

What does this code do?

  • createArray(5) is called, allocates the array on the heap, fills it, and returns. The function's own stack frame is destroyed, but the heap array lives on.
  • The loop prints each element's value and address, proving the data survived across the function boundary.
  • delete[] myArray frees the entire array. Notice the [] syntax. You use delete[] for arrays and plain delete for single values.
  • Save main.cpp.
  • You won't see a change yet. One more function to add before compiling.

Error: 'createArray' was not declared in this scope?

The createArray() helper function must appear ABOVE heapLifetimeDemo() in the file. C++ reads top-to-bottom. If the helper is below the function that calls it, the compiler does not know it exists yet.

help me fix a 'not declared in this scope' error in C++

Write the memory leak demonstration

A memory leak happens when you allocate heap memory but never free it. The operating system still thinks you are using that memory, so it cannot reclaim it. In JavaScript, the garbage collector makes leaks nearly impossible. In C++, it is entirely your responsibility.

  • Below heapLifetimeDemo(), add the memoryLeakDemo() function:
// === MEMORY LEAK DEMONSTRATION ===
void memoryLeakDemo() {
    std::cout << "\n=== MEMORY LEAK DEMONSTRATION ===" << std::endl;
    std::cout << "A leak: you allocate but never free." << std::endl;
    std::cout << "The GC makes this impossible in JS." << std::endl;
    std::cout << "In C++, it is YOUR responsibility.\n" << std::endl;

    int* leaked = new int(42);
    std::cout << "Allocated int at " << leaked
              << " with value " << *leaked << std::endl;
    std::cout << "If we never call 'delete leaked;' this memory is:" << std::endl;
    std::cout << "  - Still reserved (OS thinks we are using it)" << std::endl;
    std::cout << "  - Unreachable if we lose the pointer" << std::endl;
    std::cout << "  - Gone forever until the program exits" << std::endl;

    // In a real leak, this line would be MISSING:
    delete leaked;
    std::cout << "\nFreed! Tools like Valgrind catch forgotten deletes." << std::endl;
}

What does this code do?

  • new int(42) allocates heap memory and stores 42 there.
  • The function then explains what would happen if you never called delete. The memory stays reserved but becomes unreachable once the pointer variable goes out of scope.
  • We DO call delete leaked so our program is clean. The comment marks where a real leak would occur.

What is Valgrind?

Valgrind is a tool that monitors your program's memory usage and reports any allocations that were never freed. In production C++ code, running Valgrind is a standard practice for catching memory leaks before deployment.

Now wire all three new functions into main() so the program runs them.

  • In main.cpp, scroll down to your main() function.
  • Find this line:
    scopeDemo();
    return 0;
  • Add three function calls between scopeDemo(); and return 0;, so the block looks like this:
    scopeDemo();
    heapDemo();
    heapLifetimeDemo();
    memoryLeakDemo();
    return 0;
  • Save main.cpp.
  • Compile and run your program by running this in your terminal:
g++ -g -std=c++17 -o memory_explorer main.cpp && ./memory_explorer

You should see the stack and scope output from before, followed by new heap sections. The heap addresses will be noticeably different from the stack addresses (typically much larger numbers in a different range).

Compilation error or no output from heap functions?

  • If you see an "undeclared" error, check that heapDemo(), createArray(), heapLifetimeDemo(), and memoryLeakDemo() are all defined ABOVE main().
  • If the program compiles but only shows stack output, confirm you added heapDemo();, heapLifetimeDemo();, and memoryLeakDemo(); inside main().

help me debug my C++ heap memory program

✔️ Awesome, I've got everything!

Great. Double check you have saved main.cpp and that your output shows all three heap sections.

ⓧ I'd like to double check the full code

#include <iostream>

// === STACK MEMORY DEMONSTRATION ===
void stackDemo() {
    std::cout << "\n=== STACK MEMORY ===" << std::endl;
    std::cout << "Stack = automatic, fast, dies when scope ends." << std::endl;
    std::cout << "In JS, you never see where variables live.\n" << std::endl;

    int x = 42;
    int y = 100;
    double z = 3.14;

    std::cout << "Variable x: value = " << x
              << ", address = " << &x << std::endl;
    std::cout << "Variable y: value = " << y
              << ", address = " << &y << std::endl;
    std::cout << "Variable z: value = " << z
              << ", address = " << &z << std::endl;

    std::cout << "\nNotice: stack addresses are close together (contiguous)." << std::endl;
    std::cout << "They typically grow downward (higher to lower address)." << std::endl;
}

// === SCOPE AND AUTOMATIC CLEANUP ===
void scopeDemo() {
    std::cout << "\n=== SCOPE & AUTOMATIC CLEANUP ===" << std::endl;
    std::cout << "C++ destroys variables IMMEDIATELY at the closing brace." << std::endl;
    std::cout << "JS waits for the GC to 'get around to it'.\n" << std::endl;

    int outer = 1;
    std::cout << "outer created at " << &outer << std::endl;

    {
        int inner = 2;
        std::cout << "  inner created at " << &inner << std::endl;
        std::cout << "  Both outer (" << outer << ") and inner ("
                  << inner << ") are accessible here." << std::endl;
    }

    std::cout << "inner is now DESTROYED (went out of scope)." << std::endl;
    std::cout << "In JS: let in a block is also block-scoped," << std::endl;
    std::cout << "but cleanup is delayed until GC runs." << std::endl;
    std::cout << "In C++: cleanup is IMMEDIATE at the closing brace." << std::endl;
}

// === HEAP MEMORY DEMONSTRATION ===
void heapDemo() {
    std::cout << "\n=== HEAP MEMORY ===" << std::endl;
    std::cout << "Heap = manual, flexible, lives until YOU delete it." << std::endl;
    std::cout << "In JS, ALL objects live on the heap. GC cleans up.\n" << std::endl;

    int* heapInt = new int(99);

    std::cout << "Heap-allocated int:" << std::endl;
    std::cout << "  Value: " << *heapInt << std::endl;
    std::cout << "  Pointer address (lives on stack): " << &heapInt << std::endl;
    std::cout << "  Pointed-to address (lives on heap): " << heapInt << std::endl;

    std::cout << "\nJS equivalent:  let obj = { value: 99 };" << std::endl;
    std::cout << "C++ equivalent: int* p = new int(99);" << std::endl;
    std::cout << "Difference: YOU must call 'delete' in C++." << std::endl;

    delete heapInt;
    std::cout << "\nMemory freed with 'delete'." << std::endl;
}

// Helper: creates an array that outlives this function
int* createArray(int size) {
    int* arr = new int[size];
    for (int i = 0; i < size; i++) {
        arr[i] = i * 10;
    }
    return arr;
}

// === WHY THE HEAP EXISTS ===
void heapLifetimeDemo() {
    std::cout << "\n=== WHY THE HEAP EXISTS ===" << std::endl;
    std::cout << "Heap memory outlives the function that created it." << std::endl;
    std::cout << "Stack memory is DESTROYED when the function returns." << std::endl;
    std::cout << "In JS, ALL arrays/objects survive function returns (GC).\n"
              << std::endl;

    int* myArray = createArray(5);

    std::cout << "Array was created inside createArray() but lives on:" << std::endl;
    for (int i = 0; i < 5; i++) {
        std::cout << "  myArray[" << i << "] = " << myArray[i]
                  << " (at " << &myArray[i] << ")" << std::endl;
    }

    delete[] myArray;
    std::cout << "\nArray freed with 'delete[]'." << std::endl;
    std::cout << "Forgetting this line = MEMORY LEAK." << std::endl;
}

// === MEMORY LEAK DEMONSTRATION ===
void memoryLeakDemo() {
    std::cout << "\n=== MEMORY LEAK DEMONSTRATION ===" << std::endl;
    std::cout << "A leak: you allocate but never free." << std::endl;
    std::cout << "The GC makes this impossible in JS." << std::endl;
    std::cout << "In C++, it is YOUR responsibility.\n" << std::endl;

    int* leaked = new int(42);
    std::cout << "Allocated int at " << leaked
              << " with value " << *leaked << std::endl;
    std::cout << "If we never call 'delete leaked;' this memory is:" << std::endl;
    std::cout << "  - Still reserved (OS thinks we are using it)" << std::endl;
    std::cout << "  - Unreachable if we lose the pointer" << std::endl;
    std::cout << "  - Gone forever until the program exits" << std::endl;

    delete leaked;
    std::cout << "\nFreed! Tools like Valgrind catch forgotten deletes." << std::endl;
}

int main() {
    stackDemo();
    scopeDemo();
    heapDemo();
    heapLifetimeDemo();
    memoryLeakDemo();
    return 0;
}

Your Memory Explorer now demonstrates both sides of the memory coin. Next up, you will assemble everything into a polished program with a summary that maps these C++ concepts directly to what JavaScript was doing behind the scenes.

Assemble the Complete Memory Explorer

You have five working demonstrations that reveal how memory works behind the scenes. Now it is time to assemble them into a polished program with a clear purpose and a takeaway summary.

In this step, you will add an intro banner that sets context for anyone running the program, wire up all your demos with clean separators, and close with a comparison table that connects everything back to JavaScript and embedded systems. You will also prove your entire development environment works by debugging the program step-by-step in VS Code.

In this step, get ready to:
  • Add an intro banner and summary section to main().
  • Compile and run the complete Memory Explorer from both terminal and VS Code.
  • Set a breakpoint and debug your program step-by-step.
Add the intro banner to main()

Right now your main() function jumps straight into the demos with no context. Adding a banner tells the reader what this program is and who it is for.

  • In main.cpp, scroll to the main() function at the bottom of the file.
  • Find this line:
int main() {
    stackDemo();
  • Replace main() entirely with the following code. This adds the intro banner, keeps all your demo calls, adds the summary section, and includes an explicit return 0;:
int main() {
    // Print the program banner
    std::cout << "================================================" << std::endl;
    std::cout << "   C++ MEMORY EXPLORER" << std::endl;
    std::cout << "   For JavaScript/Node.js developers" << std::endl;
    std::cout << "================================================" << std::endl;
    std::cout << "\nThis program shows what the garbage collector" << std::endl;
    std::cout << "was hiding from you in JavaScript/Node.js.\n" << std::endl;

    // Run each demonstration in sequence
    stackDemo();
    scopeDemo();
    heapDemo();
    heapLifetimeDemo();
    memoryLeakDemo();

    // Print the summary comparison table
    std::cout << "\n================================================" << std::endl;
    std::cout << "   SUMMARY" << std::endl;
    std::cout << "================================================" << std::endl;
    std::cout << "Stack: automatic, fast, dies when scope ends" << std::endl;
    std::cout << "Heap:  manual, flexible, lives until you 'delete'" << std::endl;
    std::cout << "JS:    everything is heap + GC handles cleanup" << std::endl;
    std::cout << "C++:   you choose where data lives AND clean it up" << std::endl;
    std::cout << "\nThis is the foundation of embedded systems programming." << std::endl;
    std::cout << "When you only have 2KB of RAM, every byte matters." << std::endl;
    std::cout << "================================================" << std::endl;

    return 0;
}

What does this code do?

The banner at the top identifies the program and its audience. The summary at the bottom compares the four memory models side by side: stack, heap, JavaScript (GC-managed), and C++ (manual control).

The embedded systems line connects everything to the bigger picture. When a microcontroller has only 2KB of RAM, you cannot afford a garbage collector. You need the direct control that C++ gives you.

  • Save main.cpp.

Seeing red squiggles in VS Code?

Make sure you replaced the entire main() function including the opening int main() { and the closing }. A missing brace is the most common cause of errors here.

help me fix syntax errors in my main.cpp

Compile and run from the terminal

Time to see your complete Memory Explorer in action. You will compile from the terminal first to verify everything builds cleanly.

  • Compile your program by running this command in the VS Code integrated terminal:
g++ -g -std=c++17 -o memory_explorer main.cpp
  • Run the program by executing:
./memory_explorer
  • You should see the full output starting with the C++ MEMORY EXPLORER banner, followed by all five demonstrations, and ending with the SUMMARY comparison table.

Compilation errors?

Check that every std::cout line ends with << std::endl; and that all double-quote strings are properly closed. A single missing quote breaks the entire build.

help me fix my compilation error

Debug with VS Code's built-in debugger

Running from the terminal proves your code compiles. Debugging in VS Code proves your full development environment works end to end. You will set a breakpoint and watch a pointer variable change as you step through heap allocation.

  • In VS Code, make sure main.cpp is the active (open) file in the editor.
  • Find the line that says int* heapInt = new int(99); inside the heapDemo() function.
  • Click in the gutter (the narrow area to the left of the line number) to set a red breakpoint dot on that line.
  • Press F5 to start debugging.

What happens when you press F5?

VS Code uses your tasks.json to compile the file first (the preLaunchTask setting), then launches GDB using your launch.json configuration. The program runs until it hits your breakpoint, then pauses.

  • When the debugger pauses at your breakpoint, look at the **Variables** panel on the left side. You should see heapInt listed under **Locals**.
  • Press F10 (Step Over) to execute the current line.
  • Watch the heapInt variable's value change in the **Variables** panel. It goes from uninitialized to holding a memory address (the heap location where 99 is stored).
  • Press F5 again to continue execution until the program finishes.

Why is debugging useful here?

Stepping through the code lets you see the exact moment memory is allocated. The pointer goes from garbage (uninitialized) to a real heap address in a single step. This is the C++ equivalent of watching Node.js allocate an object, except you can see the raw address.

Debugger not stopping at your breakpoint?

Make sure main.cpp is the active file (the one with focus in the editor tab). The build task compiles the active file, so if a different file is active, the wrong binary gets built.

Also check that your breakpoint dot is solid red (not a hollow circle). A hollow circle means the debugger could not map that line to compiled code, usually because the file was not saved before building.

help me fix my VS Code debugger

✔️ Awesome, I've got everything!

Great. Double check you have saved main.cpp and that your program compiles without errors.

ⓧ I'd like to double check the full code

Here is the complete main.cpp file. Make sure yours matches this exactly.

#include <iostream>

// === STACK MEMORY DEMONSTRATION ===
void stackDemo() {
    std::cout << "\n=== STACK MEMORY ===" << std::endl;
    std::cout << "Stack = automatic, fast, dies when scope ends." << std::endl;
    std::cout << "In JS, you never see where variables live.\n" << std::endl;

    int x = 42;
    int y = 100;
    double z = 3.14;

    std::cout << "Variable x: value = " << x
              << ", address = " << &x << std::endl;
    std::cout << "Variable y: value = " << y
              << ", address = " << &y << std::endl;
    std::cout << "Variable z: value = " << z
              << ", address = " << &z << std::endl;

    std::cout << "\nNotice: stack addresses are close together (contiguous)." << std::endl;
    std::cout << "They typically grow downward (higher to lower address)." << std::endl;
}

// === SCOPE AND AUTOMATIC CLEANUP ===
void scopeDemo() {
    std::cout << "\n=== SCOPE & AUTOMATIC CLEANUP ===" << std::endl;
    std::cout << "C++ destroys variables IMMEDIATELY at the closing brace." << std::endl;
    std::cout << "JS waits for the GC to 'get around to it'.\n" << std::endl;

    int outer = 1;
    std::cout << "outer created at " << &outer << std::endl;

    {
        int inner = 2;
        std::cout << "  inner created at " << &inner << std::endl;
        std::cout << "  Both outer (" << outer << ") and inner ("
                  << inner << ") are accessible here." << std::endl;
    }

    std::cout << "inner is now DESTROYED (went out of scope)." << std::endl;
    std::cout << "In JS: let in a block is also block-scoped," << std::endl;
    std::cout << "but cleanup is delayed until GC runs." << std::endl;
    std::cout << "In C++: cleanup is IMMEDIATE at the closing brace." << std::endl;
}

// === HEAP MEMORY DEMONSTRATION ===
void heapDemo() {
    std::cout << "\n=== HEAP MEMORY ===" << std::endl;
    std::cout << "Heap = manual, flexible, lives until YOU delete it." << std::endl;
    std::cout << "In JS, ALL objects live on the heap. GC cleans up.\n" << std::endl;

    int* heapInt = new int(99);

    std::cout << "Heap-allocated int:" << std::endl;
    std::cout << "  Value: " << *heapInt << std::endl;
    std::cout << "  Pointer address (lives on stack): " << &heapInt << std::endl;
    std::cout << "  Pointed-to address (lives on heap): " << heapInt << std::endl;

    std::cout << "\nJS equivalent:  let obj = { value: 99 };" << std::endl;
    std::cout << "C++ equivalent: int* p = new int(99);" << std::endl;
    std::cout << "Difference: YOU must call 'delete' in C++." << std::endl;

    delete heapInt;
    std::cout << "\nMemory freed with 'delete'." << std::endl;
}

// Helper: creates an array that outlives this function
int* createArray(int size) {
    int* arr = new int[size];
    for (int i = 0; i < size; i++) {
        arr[i] = i * 10;
    }
    return arr;
}

// === WHY THE HEAP EXISTS ===
void heapLifetimeDemo() {
    std::cout << "\n=== WHY THE HEAP EXISTS ===" << std::endl;
    std::cout << "Heap memory outlives the function that created it." << std::endl;
    std::cout << "Stack memory is DESTROYED when the function returns." << std::endl;
    std::cout << "In JS, ALL arrays/objects survive function returns (GC).\n"
              << std::endl;

    int* myArray = createArray(5);

    std::cout << "Array was created inside createArray() but lives on:" << std::endl;
    for (int i = 0; i < 5; i++) {
        std::cout << "  myArray[" << i << "] = " << myArray[i]
                  << " (at " << &myArray[i] << ")" << std::endl;
    }

    delete[] myArray;
    std::cout << "\nArray freed with 'delete[]'." << std::endl;
    std::cout << "Forgetting this line = MEMORY LEAK." << std::endl;
}

// === MEMORY LEAK DEMONSTRATION ===
void memoryLeakDemo() {
    std::cout << "\n=== MEMORY LEAK DEMONSTRATION ===" << std::endl;
    std::cout << "A leak: you allocate but never free." << std::endl;
    std::cout << "The GC makes this impossible in JS." << std::endl;
    std::cout << "In C++, it is YOUR responsibility.\n" << std::endl;

    int* leaked = new int(42);
    std::cout << "Allocated int at " << leaked
              << " with value " << *leaked << std::endl;
    std::cout << "If we never call 'delete leaked;' this memory is:" << std::endl;
    std::cout << "  - Still reserved (OS thinks we are using it)" << std::endl;
    std::cout << "  - Unreachable if we lose the pointer" << std::endl;
    std::cout << "  - Gone forever until the program exits" << std::endl;

    delete leaked;
    std::cout << "\nFreed! Tools like Valgrind catch forgotten deletes." << std::endl;
}

int main() {
    std::cout << "================================================" << std::endl;
    std::cout << "   C++ MEMORY EXPLORER" << std::endl;
    std::cout << "   For JavaScript/Node.js developers" << std::endl;
    std::cout << "================================================" << std::endl;
    std::cout << "\nThis program shows what the garbage collector" << std::endl;
    std::cout << "was hiding from you in JavaScript/Node.js.\n" << std::endl;

    stackDemo();
    scopeDemo();
    heapDemo();
    heapLifetimeDemo();
    memoryLeakDemo();

    std::cout << "\n================================================" << std::endl;
    std::cout << "   SUMMARY" << std::endl;
    std::cout << "================================================" << std::endl;
    std::cout << "Stack: automatic, fast, dies when scope ends" << std::endl;
    std::cout << "Heap:  manual, flexible, lives until you 'delete'" << std::endl;
    std::cout << "JS:    everything is heap + GC handles cleanup" << std::endl;
    std::cout << "C++:   you choose where data lives AND clean it up" << std::endl;
    std::cout << "\nThis is the foundation of embedded systems programming." << std::endl;
    std::cout << "When you only have 2KB of RAM, every byte matters." << std::endl;
    std::cout << "================================================" << std::endl;

    return 0;
}

Your Memory Explorer is complete. You now have a working C++ program that makes the invisible visible, and a development environment that lets you build, run, and debug from both the terminal and VS Code. Next up is a secret mission where you will explore the dangerous side of manual memory management.

Secret mission

Dangerous Memory - Dangling Pointers and Buffer Overflows

Your Memory Explorer covers the safe patterns. But real C++ code faces dangers that JavaScript developers have never encountered. Add two demonstrations that show what happens when you access freed memory and write past array bounds. These are the bugs that crash embedded systems and cause security vulnerabilities.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your tools installed for future C++ projects, or remove them entirely.

Resources you used:

  • memory-explorer project folder on your Desktop.
  • gcc-c++ package (GCC 15 compiler).
  • gdb package (GNU debugger).
  • Microsoft C/C++ extension in VS Code.

Keep everything running

No action needed. Choose this if you plan to continue with Week 2 (pointers and references in depth).

  • Your memory-explorer project folder can serve as a template for future C++ projects. Copy the .vscode folder into any new project to reuse your build and debug configuration.
  • GCC, GDB, and the C/C++ extension are all free and take minimal disk space.

Pause - I'll come back to this later

Since everything runs locally and nothing consumes resources when idle, there is nothing to pause. Your tools and files sit quietly on disk until you return.

  • No running processes to stop. The compiled binary only runs when you execute it.
  • Keep your memory-explorer folder as-is so you can pick up where you left off.

Delete - I don't want to use this again

Remove all project files and uninstall the tools you installed during this project.

  • Delete the project folder by running:
cd ~/Desktop
rm -rf memory-explorer
  • Uninstall the C++ compiler and debugger by running:
sudo dnf remove gcc-c++ gdb
  • Uninstall the C/C++ extension in VS Code. Press Ctrl+Shift+X to open Extensions, find C/C++ by Microsoft, click the gear icon, and select Uninstall.

That's a Wrap!

That's a Wrap!

Nice work! You just built a C++ Memory Explorer that makes the invisible visible. Memory addresses, allocation regions, and manual cleanup are no longer mysteries hidden behind a garbage collector.

You've learned how to:

  • Installed and configured a complete C++ development environment on Fedora 42 with GCC 15, GDB, and VS Code with the C/C++ extension.
  • Wrote a Memory Explorer program demonstrating stack memory (automatic, scope-bound, contiguous addresses) and heap memory (manual allocation with new, manual cleanup with delete).
  • Identified memory leak patterns and understood what JavaScript's garbage collector handles automatically behind the scenes.
  • Secret Mission: Demonstrated dangling pointers (use-after-free) and buffer overflows, two critical bug classes in systems programming.

Ready to quiz yourself?