Beginner

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:

SectionPurposeContents
.dataInitialized dataBytes with a defined starting value
.bssUninitialized dataReserved space filled with zeros at load time
.textCodeExecutable 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:

DirectiveSizeUse
db1 byteCharacters, small integers, byte arrays
dw2 bytes16-bit integers, words
dd4 bytes32-bit integers, double words
dq8 bytes64-bit integers, quad words
resb NN bytesReserve N uninitialized bytes (.bss only)
resd NN × 4 bytesReserve 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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
section .data
    ; A single byte: value 65 (ASCII 'A')
    my_byte     db 65

    ; A 32-bit integer: value 42
    my_int      dd 42

    ; A string (array of bytes) with a newline at the end
    my_string   db "Assembly variables demo", 10
    str_len     equ $ - my_string

    ; An assembler constant — no memory is allocated
    MY_CONST    equ 100

    ; A label for the done message
    done_msg    db "Data defined in .data section", 10
    done_len    equ $ - done_msg

section .text
    global _start

_start:
    ; Print the done message to show the program runs
    mov eax, 4          ; sys_write
    mov ebx, 1          ; stdout
    mov ecx, done_msg   ; pointer to message
    mov edx, done_len   ; length
    int 0x80

    ; Exit cleanly
    mov eax, 1          ; sys_exit
    xor ebx, ebx        ; exit code 0
    int 0x80

Running with Docker

1
2
3
4
5
# Pull the NASM image
docker pull esolang/x86asm-nasm:latest

# Assemble, link, and run
docker run --rm -v $(pwd):/code -w /code esolang/x86asm-nasm x86asm-nasm variables.asm

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
section .data
    result_msg  db "Sum: ", 0
    msg_len     equ $ - result_msg - 1   ; exclude the null terminator
    newline     db 10
    nl_len      equ 1

section .bss
    ; Reserve space for two 32-bit integers
    num_a   resd 1      ; 4 bytes for first operand
    num_b   resd 1      ; 4 bytes for second operand
    result  resd 1      ; 4 bytes for the result

section .text
    global _start

_start:
    ; Store values into our .bss "variables"
    mov dword [num_a], 3    ; num_a = 3
    mov dword [num_b], 4    ; num_b = 4

    ; Load them into registers and add
    mov eax, [num_a]        ; eax = 3
    mov ebx, [num_b]        ; ebx = 4
    add eax, ebx            ; eax = 3 + 4 = 7

    ; Store the result back to memory
    mov [result], eax       ; result = 7

    ; Print "Sum: "
    mov eax, 4
    mov ebx, 1
    mov ecx, result_msg
    mov edx, msg_len
    int 0x80

    ; Convert result (7) to ASCII ('7' = 7 + 48) and print it
    mov eax, [result]       ; reload result into eax
    add eax, 48             ; convert to ASCII digit
    mov [result], eax       ; store back (reusing the memory as a char buffer)

    mov eax, 4
    mov ebx, 1
    mov ecx, result         ; pointer to the ASCII digit
    mov edx, 1              ; print one byte
    int 0x80

    ; Print newline
    mov eax, 4
    mov ebx, 1
    mov ecx, newline
    mov edx, nl_len
    int 0x80

    ; Exit
    mov eax, 1
    xor ebx, ebx
    int 0x80

Running with Docker

1
2
docker pull esolang/x86asm-nasm:latest
docker run --rm -v $(pwd):/code -w /code esolang/x86asm-nasm x86asm-nasm variables_bss.asm

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:

RegisterBytesCommon Use
eax4Accumulator; arithmetic results; syscall number
ebx4Base; first syscall argument
ecx4Counter; second syscall argument
edx4Data; third syscall argument
esi4Source index for string/memory operations
edi4Destination index
esp4Stack pointer (avoid overwriting)
ebp4Base 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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
section .data
    label_a     db "Byte in al: ", 0
    label_a_len equ $ - label_a - 1
    label_b     db "Word in ax: done", 10, 0
    label_b_len equ $ - label_b - 1
    newline     db 10

section .bss
    char_buf    resb 1      ; one byte buffer for printing

section .text
    global _start

_start:
    ; --- Byte example: store 'Z' (ASCII 90) in al ---
    mov al, 90              ; load byte value into al (low byte of eax)

    ; Print label
    mov eax, 4
    mov ebx, 1
    mov ecx, label_a
    mov edx, label_a_len
    int 0x80

    ; Store al value in buffer and print it
    mov byte [char_buf], 90     ; 'Z'
    mov eax, 4
    mov ebx, 1
    mov ecx, char_buf
    mov edx, 1
    int 0x80

    ; Print newline
    mov eax, 4
    mov ebx, 1
    mov ecx, newline
    mov edx, 1
    int 0x80

    ; --- Word example: load a 16-bit value into ax ---
    mov ax, 1000            ; 16-bit value; only lower 16 bits of eax are touched

    ; Print confirmation label
    mov eax, 4
    mov ebx, 1
    mov ecx, label_b
    mov edx, label_b_len
    int 0x80

    ; Exit
    mov eax, 1
    xor ebx, ebx
    int 0x80

Running with Docker

1
2
docker pull esolang/x86asm-nasm:latest
docker run --rm -v $(pwd):/code -w /code esolang/x86asm-nasm x86asm-nasm variables_regs.asm

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:

InterpretationExample instructionWhat it means
Unsigned integermov eax, 255Treat bits as a positive number
Signed integerimul eax, ebxTreat high bit as a sign bit
ASCII charactermov al, 65Byte value 65 → printed as ‘A’
Memory addressmov ecx, my_stringValue is an address to dereference
Boolean flagcmp eax, 0 sets ZFZero 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.
  • .data section: Holds initialized named storage; each label is an assembler symbol for a memory address.
  • .bss section: Reserves zero-filled space for “variables” written at runtime; uses resb/resw/resd/resq directives.
  • equ constants: 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, and eax are overlapping views of the same physical register; writing al changes the low byte of eax.
  • Memory access uses square brackets: mov eax, [num_a] reads from the address num_a; mov eax, num_a loads the address itself.
  • dword/byte/word size hints: When the operand size is ambiguous (e.g., writing to a memory location), NASM requires a size qualifier like mov dword [addr], 42.

Running Today

All examples can be run using Docker:

docker pull esolang/x86asm-nasm:latest
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining