Variables and Types in Assembly
Learn how Assembly stores and manages data using registers, memory sections, and data directives in NASM x86
Assembly language has no variables in the high-level sense — no int x = 42 or string name = "Alice". Instead, data lives in two places: registers (tiny, ultra-fast storage inside the CPU) and memory (RAM, accessed by address). Everything a program “remembers” must be explicitly placed into one of these locations and retrieved by the programmer’s own instructions.
This low-level reality is what makes Assembly both powerful and demanding. There is no type system enforcing correctness; a sequence of bytes can be treated as a number, a character, an address, or anything else depending on the instructions you use. The programmer decides what each chunk of memory means.
In this tutorial you will learn how NASM x86 Assembly organizes data using the .data and .bss sections, how data directives define the size and initial value of each piece of storage, and how registers serve as the workhorses that hold values during computation.
Memory Sections: Where Data Lives
Before looking at code, it helps to understand the three sections every NASM program can use:
| Section | Purpose | Contents |
|---|---|---|
.data | Initialized data | Bytes with a defined starting value |
.bss | Uninitialized data | Reserved space filled with zeros at load time |
.text | Code | Executable instructions |
“Variables” in Assembly are simply named locations (labels) in .data or .bss. The label gives you a symbolic name for the address; the assembler replaces it with the actual memory address when it assembles the file.
Data Directives: Defining Size
NASM provides directives that define how many bytes a label occupies and (for .data) what the initial value is:
| Directive | Size | Use |
|---|---|---|
db | 1 byte | Characters, small integers, byte arrays |
dw | 2 bytes | 16-bit integers, words |
dd | 4 bytes | 32-bit integers, double words |
dq | 8 bytes | 64-bit integers, quad words |
resb N | N bytes | Reserve N uninitialized bytes (.bss only) |
resd N | N × 4 bytes | Reserve N uninitialized double words |
equ | (constant) | Define an assembler-time constant (no memory allocated) |
Example 1: Initialized Data in .data
This program defines several “variables” in the .data section — a byte, a 32-bit integer, and a string — then prints a confirmation message so you can see the program runs to completion.
Create a file named variables.asm:
| |
Running with Docker
| |
Expected Output
Data defined in .data section
Example 2: Uninitialized Storage with .bss and Register Arithmetic
The .bss section reserves space that the OS zero-fills at load time. This is where you declare “variables” that will be written to during execution — equivalent to declaring but not initializing a variable in C.
This example reserves space for two 32-bit integers, loads values into registers, adds them, stores the result in memory, then prints it as a single ASCII digit.
Create a file named variables_bss.asm:
| |
Running with Docker
| |
Expected Output
Sum: 7
Registers: The Fastest “Variables”
Registers are the most important storage in Assembly. They live inside the CPU and are accessed in a single clock cycle — far faster than any memory access. In 32-bit x86 mode (which this Docker image uses), the general-purpose registers are:
| Register | Bytes | Common Use |
|---|---|---|
eax | 4 | Accumulator; arithmetic results; syscall number |
ebx | 4 | Base; first syscall argument |
ecx | 4 | Counter; second syscall argument |
edx | 4 | Data; third syscall argument |
esi | 4 | Source index for string/memory operations |
edi | 4 | Destination index |
esp | 4 | Stack pointer (avoid overwriting) |
ebp | 4 | Base pointer for stack frames |
Each 32-bit register (eax) has 16-bit (ax) and 8-bit (ah, al) sub-registers that access the lower portion of the same physical register:
31 16 15 8 7 0
+---------+-------+------+
| upper | ah | al | ← parts of eax
+---------+-------+------+
| ax |
| eax |
Example 3: Working with Byte and Word Sub-registers
Assembly’s “types” come from which register width and directive you choose. This example stores a character into al (the low byte of eax) and prints it, demonstrating byte-level access.
Create a file named variables_regs.asm:
| |
Running with Docker
| |
Expected Output
Byte in al: Z
Word in ax: done
How Assembly “Types” Work
Because there is no type system, the same memory or register can be interpreted as different kinds of data depending on the instruction:
| Interpretation | Example instruction | What it means |
|---|---|---|
| Unsigned integer | mov eax, 255 | Treat bits as a positive number |
| Signed integer | imul eax, ebx | Treat high bit as a sign bit |
| ASCII character | mov al, 65 | Byte value 65 → printed as ‘A’ |
| Memory address | mov ecx, my_string | Value is an address to dereference |
| Boolean flag | cmp eax, 0 sets ZF | Zero Flag in EFLAGS register acts as bool |
The programmer is responsible for consistency. Nothing prevents you from loading a string address into eax and trying to do arithmetic with it — the CPU will happily comply and produce garbage.
Key Concepts
- No type system: Assembly has no compiler-enforced types. Data directives (
db,dw,dd,dq) declare size in bytes, not semantic type. .datasection: Holds initialized named storage; each label is an assembler symbol for a memory address..bsssection: Reserves zero-filled space for “variables” written at runtime; usesresb/resw/resd/resqdirectives.equconstants: Assembler-time constants evaluated at assembly time — no memory is allocated and the value cannot change at runtime.- Registers are primary working storage: Load values into registers to operate on them; store results back to memory when you need to preserve them.
- Sub-registers share storage:
al,ax, andeaxare overlapping views of the same physical register; writingalchanges the low byte ofeax. - Memory access uses square brackets:
mov eax, [num_a]reads from the addressnum_a;mov eax, num_aloads the address itself. dword/byte/wordsize hints: When the operand size is ambiguous (e.g., writing to a memory location), NASM requires a size qualifier likemov dword [addr], 42.
Running Today
All examples can be run using Docker:
docker pull esolang/x86asm-nasm:latest
Comments
Loading comments...
Leave a Comment