Foundations · Module 1 · Lesson 3 of 9
Variables Are Names for Bytes
See a variable as what it really is: a named, typed region of bytes. Trace four lines of code through eight bytes of memory, including how numbers are laid out and what copying does.
By the end you can
- Explain a variable as a named, typed region of bytes that exists only as an address after compilation
- Predict the bytes a small integer occupies, including little-endian order
- Distinguish initialization from assignment and predict what copying between variables does
- Describe what an uninitialized local variable contains and why reading it is dangerous
Builds on Anatomy of Your First Program.
01 · A question first
The compiler turns int count = 5 into machine code, yet the finished program contains no word count anywhere. So how does the program find the 5 again, and what exactly changes in memory when you write count = count + 1?
Make a guess, then step through the trace below to check it.
02 · Watch it run
int a = 7;int b = a;b = b + 1;a = 300;- byte 0--
- byte 1--
- byte 2--
- byte 3--
- byte 4--
- byte 5--
- byte 6--
- byte 7--
Memory is a long row of bytes, each with an address and each holding a number from 0 to 255. We watch eight of them, written in hexadecimal. Nothing is stored yet and no name exists at run time: the compiler has already decided that a will use bytes 0 to 3 and b bytes 4 to 7.
Machine work: no code has run; the compiler fixed the layout while compiling
03 · The idea
Start with the problem
The program from the last lesson printed 5 and forgot it. Real programs have to remember things: a score, a name, the number of items in a cart. For that they need somewhere to keep values, and a way to find them again.
Hardware offers memory: a long row of bytes, each holding one number from 0 to 255 and each identified only by its position, called an address. Memory has no names, no types, and no idea what any byte means. The byte at address 4096 does not know whether it belongs to a score or to a letter.
People want names and machines want addresses. A variable is the bridge between them, and C++ is unusually honest about how thin that bridge is. Here is the smallest example:
int apples = 3;
apples = apples + 2;Two lines, and they tell the machine to reserve space, put 3 there, then read it, add 2 and write 5 back. The rest of this lesson is the detail behind those steps.
How the idea works
The trace above watches eight bytes of memory while four lines run. The bytes are written in hexadecimal, two digits per byte from 00 to FF, which is how programmers usually read memory.
A variable is a typed region of bytes. In int a = 7;, the type int says how many bytes to reserve and how to read them, a is the name you use in the source, and 7 is the starting value. On mainstream systems an int takes four bytes, and sizeof(a) reports that. The region of memory together with its type is what C++ calls an object; a variable is a name for one.
Where the bytes are. A local variable lives on the stack, a region of memory that functions use for their working storage. When a function starts, the program reserves one block there for all of its local variables by adjusting a single register, and the compiler decides which variable sits at which offset in that block. Bytes 0 to 3 and 4 to 7 in the trace stand for two such slots.
How values become bytes. The number 7 in four bytes is 07 00 00 00, not 00 00 00 07. Most machines you will use, including x86-64 and in practice nearly all ARM systems, are little-endian: the least significant byte is stored at the lowest address. That is why 300, which is 0x012C, appears as 2C 01 00 00 in the fifth step of the trace.
Initialization versus assignment. int a = 7; is a declaration with initialization: it creates the variable and gives it its first value in one go. a = 300; is an assignment: the variable already exists, and its bytes are overwritten. They look alike and do different things. C++ also lets you initialize with braces, as in int a{7};, which has a useful property: it refuses to lose information silently. int x{3.5}; is a compile error, while int x = 3.5; compiles and stores 3, though some compilers warn.
Copying. int b = a; reads the four bytes of a and writes them into b. Afterwards b is a separate region with its own copy. The third step of the trace shows the consequence: changing a later does not change b.
Reading and writing. The CPU does not do arithmetic directly on memory. For b = b + 1 it loads the bytes of b into a register, a tiny storage cell inside the CPU, adds 1 there, and stores the result back. Loading, computing in registers and storing is all that using variables ever amounts to.
The names are gone. The last step of the trace is the most important one: the finished program contains no a and no b. The compiler replaced each name with an address or an offset, and the machine code only says to load or store four bytes there. Names survive only in the optional debug information you request with -g, a separate table that the debugger reads.
You can look at all of this from inside a program:
#include <iostream>
int main() {
int a = 7;
int b = a;
std::cout << sizeof(a) << "\n";
std::cout << &a << " " << &b << "\n";
}4
0x7ffc2b3a9e5c 0x7ffc2b3a9e58Here sizeof(a) is the number of bytes a occupies, and &a is its address. The addresses will differ between machines and even between runs, but each int sits on a four-byte boundary, so the two are a multiple of four apart.
Why it is correct
The model works because three decisions are made at compile time, so none of them costs anything while the program runs. The type fixes the size and the meaning: the same four bytes 07 00 00 00 are the integer 7 when read as an int, and something else entirely when read as a floating-point number or as four separate characters. The compiler resolves every name to an address, so reading a variable is one load instruction with no lookup by name. And the compiler knows each variable's lifetime from the braces around it: its bytes are reserved when its scope begins and free to reuse when the scope ends, which is why no cleanup code is needed for these variables.
Copy behavior follows from the same picture. b = a moves bytes from one region into another, and two regions know nothing about each other. This is called value semantics, and it is the default for every built-in type in C++ and for most library types. Many other languages make assignment copy a reference to a shared object instead. C++ makes you ask for sharing explicitly, with references and pointers, which later lessons introduce.
What it costs
Time. Reading or writing a local int is typically a single instruction, and the bytes are almost always in a register or the CPU's fastest cache, so the cost is around a nanosecond or less. Variables are about as cheap as computation gets.
Space. Each variable costs sizeof bytes, plus padding so that it sits on a boundary its type prefers: an int is aligned to four bytes and a double to eight. A million int values cost about 4 MB, and a million double values about 8 MB.
The stack is small. The stack that holds local variables has a fixed limit, commonly 8 MB on Linux and 1 MB on Windows by default. A local array of three million ints is about 12 MB and would overflow an 8 MB stack, crashing the program. Large data belongs elsewhere, and a later module shows where.
The model versus the optimizer. The trace shows the model, not a promise. When you compile with optimization, the compiler often keeps a variable in a register and never gives it memory at all, or deletes it if its value is never used. Taking its address with & forces it to exist in memory, which is one reason that code full of address-taking can be slower.
Pitfalls and variations
The uninitialized variable. int total; inside a function reserves four bytes and puts nothing in them. They hold whatever an earlier function left there, and reading them is undefined behavior: the language makes no promise about what happens. The output may look reasonable on Monday and be wrong on Tuesday. Turn on warnings with -Wall -Wextra, and initialize every variable where you declare it. A habit from other places misleads here: variables declared outside any function, which you will meet later, are zeroed automatically, but locals are not.
Assuming the layout. The trace puts a before b. A real compiler may place them in either order, with padding in between, and often puts later variables at lower addresses. Never write code that relies on two variables being neighbors in memory.
Confusing assignment with comparison. A single equals sign assigns, and a double equals sign compares. Writing if (a = 5) assigns 5 to a and then tests the result as true, which is almost never what you meant. Compilers warn about it when you ask for warnings.
Overflow. The four bytes of an int cannot hold more than 2,147,483,647. Adding 1 to that value is undefined behavior for signed integers, not a tidy wraparound. The next lesson looks at how types set these limits.
Variations. const int limit = 10; makes the compiler reject any later assignment to limit. auto n = 7; lets the compiler deduce the type, here int. int a = 1, b = 2; declares two variables in one statement. All of them follow the same rule: a name, a type and a region of bytes.
Where it is used
Everywhere. Every pointer and reference you will meet in the next modules is built on the idea of a variable that holds or stands for an address. Debuggers show variable names by reading the debug table that maps each name to an address and a type. Memory bugs such as reading uninitialized bytes, using a variable after its scope has ended, or running past the end of an array are all violations of the picture in the trace, which is why it is worth keeping in your head.
The next lesson, "Types Decide What Bits Mean", looks at how the same four bytes can be an integer, a floating-point number or four characters, depending only on the type used to read them.
04 · Prove it
Question 1 of 4 · answer from memory
After compilation, which of these does the machine code for `int count = 5;` actually contain?
05 · Practice
Asked in interviews
Something wrong with this lesson? Report a problem