Covering Up

We’ve already discussed lot of different ingredients that are needed to write an assembly program or to solve problems. But here’s the truth — we still haven’t really gotten our hands dirty.

Why? Well, because we’re still missing some of the juiciest and most compulsory ingredients of assembly programming. Can you guess what they are?

That’s right — our old-school friends: variables, arrays, and the long-awaited stack (yeah, finally!).

Up to this point, we’ve only touched on these topics a little bit, sometimes just giving a passing reference. But from here on out, we’ll dive into them in detail and really understand how they work in assembly.

Variables

What are variables in terms of assembly language

In assembly language, a variable is nothing more than a memory address that we give a name for our ease. In reality, the assembler converts that name into the actual memory address during execution. So when we use a variable name, the CPU is really working with its memory address.

Why we use variables

Registers are very fast but limited in number and size. They only come in 8-bit, 16-bit, 32-bit, and 64-bit sizes depending on the architecture. When we need to store larger amounts of data or more data than registers can handle, we use variables in memory.

How we declare a variable

In 16-bit assembly, variables are usually declared after the program’s DOS exit instructions:

mov ax, 0x4c00
int 0x21

After these instructions, we can define variables.

Syntax

General syntax for declaring variables is:

name:  directive  value

Where:

  • name → the variable name (label).

  • directive → tells the assembler the size of the variable. Common ones are:

    • DB (Define Byte → 8 bits)

    • DW (Define Word → 16 bits)

    • DD (Define Doubleword → 32 bits)

    • DQ (Define Quadword → 64 bits)

  • value → the initial value assigned to the variable.

Example (Warmup Code Extension)

Let’s extend our warmup code by declaring a variable and moving data into it:

[org 0x100]

mov ax, [num1]     ; move the value of num1 into AX
add ax, 5          ; add 5 to it

mov ax, 0x4c00     ; exit program
int 0x21

num1 db 10         ; declare a variable num1 with value 10

Here:

  • num1 is the variable name.

  • db tells the assembler to reserve 1 byte(8 bits) of memory.

  • 10 is the initial value stored there.

During execution, the assembler replaces num1 with the actual memory address.

Note on Syntax

When declaring variables in assembly, you might see two different styles:

num1: dw 16     ; Recommended — colon clearly marks the label
num1 dw 16      ; Works, but less explicit

Both forms are often accepted by assemblers, but the colon (:) is the standard way to define a label. It makes the code cleaner and avoids possible warnings or confusion.

👉 So, always prefer:

num1: dw 16

Things to remember about variables

  • Assembly does not allow memory-to-memory operations directly (As we mentioned in Addressing Modes). We must first load data from memory into a register, then operate on it, and store it back if needed.

    mov ax, [num1]     ; valid
    mov [num2], ax     ; valid
    mov [num1], [num2] ; ❌ invalid
  • A variable is just a label for an address. The assembler only cares about addresses, not names.

  • The size directive (db, dw, dd, dq) is very important, because it tells the assembler how many bytes to reserve in memory.

  • If the variable or address is written inside [] brackets, it means we want to get the value stored at that address.

  • If it is written without [], it means we want to store the address itself into the register.

    mov ax, [num1]       ; Store value 10 in AX
    mov bx, num1         ; Store the actual address of num1 in BX
Arrays

What are arrays in terms of assembly language

In assembly, an array is simply a continuous block of memory where elements are stored one after another. Each element is accessed by its offset (index) from the starting address. The assembler does not treat an array as a special type — it is just a sequence of variables stored side by side.

Why we use arrays

When we need to store multiple values of the same type (like numbers, characters, etc.) in memory, instead of declaring multiple variables, we use arrays. This way we can process data in a loop, using the index to move through each element.

How we declare an array

Arrays are declared in the same way as variables, but instead of a single value, we list multiple values separated by commas.

  • Just like variables, they are declared after DOS exit instructions in the data section.

Syntax

name:  directive  value1, value2, value3, ...

Example directives:

  • DB → array of bytes

  • DW → array of words

  • DD → array of doublewords

Example (Warmup Code Extension)

Let’s declare an array of numbers and access its elements:

[org 0x100]

mov si, numbers    ; load address of array into SI
mov al, [si]       ; load first element into AL (10)
add al, 5          ; add 5 → AL = 15

mov ax, 0x4c00     ; exit program
int 0x21

numbers db 10, 20, 30, 40, 50   ; declare an array of 5 bytes

Here:

  • numbers is the starting address of the array.

  • db allocates 5 bytes in memory.

  • Elements are stored as: [numbers] = 10, [numbers+1] = 20, [numbers+2] = 30, and so on.

  • Accessing array elements

    • First element → [numbers]

    • Second element → [numbers+1]

    • Third element → [numbers+2]

    Access is done using base address + index (offset).

Things to remember about arrays

  • Arrays do not have a fixed “size” property. The programmer must remember the number of elements.

  • Indexing is always done through offsets from the base address.

  • Arrays must use the same data size (db, dw, etc.) for all elements, otherwise calculations will be incorrect.

  • Accessing beyond the declared size can lead to unexpected results because it will just read whatever is in the next memory location.

Stack

Throughout our notes, we’ve mentioned the stack multiple times in small pieces. For example:

  • When we studied registers, we met the SP (Stack Pointer) register.

  • While discussing subroutines, we saw how the CPU automatically pushes the next instruction address onto the stack before jumping into a procedure.

  • Even system control instructions, we talked about use interrupts also to save the current state on the stack .

So, the stack has always been there in the background. Now it’s time to properly understand it and see how we can use it in assembly programming.

What is the stack in terms of assembly language

The stack is a data structure that works in a Last In, First Out (LIFO) manner. That means the last value pushed onto the stack is the first one to be popped off.

In assembly, the stack is a specific section of memory that is managed using two special registers:

  • SS (Stack Segment) → holds the starting address of the stack segment.

  • SP (Stack Pointer) → always points to the top of the stack.

Fig 5.0: Stack Layout

Why we use the stack

  • To temporarily save data (like register values) during program execution.

  • To store the return address when calling subroutines.

  • To handle interrupts and context switching.

  • To pass parameters between functions.

The stack is crucial because registers are limited. If we need to preserve values while performing other operations, we offload them to the stack.

Stack Operations

The two main instructions that work with the stack are PUSH and POP.

  • PUSH

    This instruction use to send or stores the data onto the stack. When something is pushed into the stack SP register decreases (-2) automatically (because the stack grows downward).

  • POP

    This instruction retrieves the last stored value (top of stack) from the stack back into a register.

    • The value at the top of the stack is copied into the register.

    • The SP register increases automatically (+2 because we remove a value).

Example (Simple PUSH and POP)

[org 0x100]

mov ax, 5       ; AX = 5
mov bx, 10      ; BX = 10

push ax         ; push value of AX (5) onto stack
push bx         ; push value of BX (10) onto stack

pop bx          ; pop last value (10) into CX
pop dx          ; pop next value (5) into DX

mov ax, 0x4C00  ; exit program
int 0x21

We can also manually create and configure our own stack by defining a separate memory segment for it and setting up SS (Stack Segment) and SP (Stack Pointer).

However, this topic is a bit more complex, so I’m not covering it in these notes.

Once you feel confident with stack operations (PUSH , POP), I highly recommend learning about manual stack setup on your own — it will give you much deeper control over how your programs handle data.

Last updated

Was this helpful?