Declaring and Initializing Variables
Variables and Data Types

1.1 Declaring and Initializing Variables

Variables are names for typed objects or values in a C++ program. A declaration tells the compiler the type and scope; the implementation may place an object in memory, keep it in a register, or optimize it away when its observable value is unnecessary.

Unlike JavaScript or Python where variables can hold a string, then a float, then an array because they have type identity crises, C++ is a strongly typed language. Once a variable is declared as an integer, it is an integer forever. It cannot marry a string later. If you try, the compiler will aggressively object at the wedding.

The Stack Frame and Memory Layout

Automatic local variables have automatic storage duration: they are created as execution enters their scope and destroyed as it leaves. A typical unoptimized implementation uses a stack frame, but an optimizer may keep a value in a register, combine it with another value, or remove it entirely. Lifetime is a language rule; stack placement is an implementation detail.

Here is what stack memory physically looks like when declaring an integer x set to 5:

Address          Memory Contents       Variable name
0x7ffee3c8a1b0   [ 05 00 00 00 ]       x (4 bytes)
0x7ffee3c8a1b4   [ xx xx xx xx ]       Padding / Unallocated
0x7ffee3c8a1b8   [ rbp address ]       Saved Stack Frame Pointer
Stack frame memory layout for int x{5}.

The Pitfall of Uninitialized Variables (Ghost Memory)

When you ask the computer for memory to store a variable, the operating system gives you a slice of RAM. However, C++ does not automatically zero out or clean that RAM. It just hands it to you, dirty dishes and all.

If you declare a variable without giving it a value, that storage is indeterminate until you assign to it. On typical systems the bytes are whatever your own process last left in that stack slot, not data from another program (modern OSes zero pages before handing them to a new process). Reading an indeterminate value is undefined behavior (UB). Your program might print a random number, crash, or behave inconsistently; the compiler makes no guarantees.

#include <iostream>

int main() {
    int x; // Uninitialized! Contains garbage values.
    std::cout << x << std::endl; // DANGEROUS: Prints whatever garbage was in that memory location.
    return 0;
}
The danger of uninitialized variables.

Modern Initialization in C++

To avoid garbage values, I recommend that you always initialize your variables when you declare them. C++ supports three main initialization styles, but modern C++ strongly advocates for one in particular:

1. Copy Initialization (C style)

int width = 5;
Copy initialization syntax.

This is inherited from C. It copies the value on the right into the variable on the left. It works fine for simple types. For complex objects, C++17 guaranteed copy elision often eliminates temporaries entirely when you initialize directly from a prvalue (e.g. std::string s = makeName();). Brace initialization remains the safer default for narrowing checks.

2. Direct Initialization

int width(5);
Direct initialization syntax.

Direct initialization looks like a function call. It is rarely used in modern code because it has been superseded by uniform initialization, and it occasionally tricks the compiler into thinking you are declaring a function (the infamous 'most vexing parse').

3. Brace (Uniform) Initialization

int width{5};
Brace (uniform) initialization syntax.

Introduced in C++11, brace initialization (also known as list initialization) is the recommended modern standard for initializing variables. It has two massive advantages:

  • Value Initialization: If you leave the braces empty (e.g. int width{};), C++ guarantees the variable is initialized to its default value (which is 0 for numbers). No more garbage memory ghosts!
  • Prevents Narrowing Conversions: If you try to assign a floating point number (like 4.5) to an integer using copy or direct initialization, the compiler will silently slice off the decimal and make it 4. Using brace initialization, the compiler will throw a compilation error, saving you from subtle bugs that ruin weekends.

Here is what the compiler actually throws if you try to assign a double to an int using brace initialization:

error: narrowing conversion of '4.5e+0' from 'double' to 'int' inside { } [-Wnarrowing]
   int z{4.5};
         ^~~
Compilation error generated for narrowing conversions.

The Takeaway

When we write modern C++, we must build the habit of using brace initialization for all our variable declarations. It is safer, uniform, and makes your code predictable.

int apples{10};    // Explicitly set to 10
int oranges{};     // Automatically initialized to 0
double pi{3.1415}; // Explicitly set to 3.1415
Modern brace initialization examples.
Finished reading this lesson?