100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

Structures in Assembly

How fixed-layout structures are declared with NASM's struc directive, addressed by named offset, nested, and arrayed in x86-64 assembly.

Memory & AddressingAdvanced10 min readJul 10, 2026
Analogies

Structures in Assembly

A structure in assembly is a fixed layout of named byte offsets within a contiguous block of memory, grouping fields of possibly different sizes under one label the way a C struct does, except the assembler has no built-in concept of 'struct' — the programmer (or the assembler's macro facility) defines the offsets by hand. NASM's struc/endstruc directives let you declare field names that expand to constant offsets, so mov eax, [rsi + Point.y] reads a field by name instead of a hardcoded magic number, which is purely a readability aid; the generated machine code is identical to using the raw offset.

🏏

Cricket analogy: A player's scorecard entry with fixed columns for runs, balls faced, and fours, always in the same column order for every player, mirrors a structure's fixed field layout at constant offsets.

Declaring Structures with struc

The struc directive computes each field's offset automatically based on the cumulative size of the fields declared before it, so reordering fields changes every subsequent offset — this is why struct layout changes are binary-incompatible across separately assembled modules unless everything is rebuilt together. Padding and alignment are not automatic either; if a struct mixes byte and quadword fields, the programmer (or an alignment-aware macro) must insert explicit padding to keep multi-byte fields on aligned addresses, since misaligned accesses on x86-64 are legal but slower, and mandatory on some architectures.

🏏

Cricket analogy: Reordering a batting lineup mid-series changes every subsequent batting position by one slot, exactly like reordering struct fields shifts every following field's offset.

nasm
struc Point
    .x:     resd 1          ; offset 0, 4 bytes
    .y:     resd 1          ; offset 4, 4 bytes
endstruc

struc Player
    .pos:       resb Point_size   ; offset 0, embedded Point (8 bytes)
    .health:    resd 1            ; offset 8
    .name:      resb 16           ; offset 12, fixed 16-byte buffer
endstruc

section .data
    p1:     istruc Player
                at Player.pos + Point.x, dd 100
                at Player.pos + Point.y, dd 200
                at Player.health,        dd 100
            iend

section .text
global _start
_start:
    mov     eax, [p1 + Player.pos + Point.x]   ; eax = 100
    mov     ebx, [p1 + Player.health]          ; ebx = 100

    mov     eax, 60
    xor     edi, edi
    syscall

Field names generated by struc (like Player.health) are just assembler-time constants equal to a byte offset — Player.health assembles to the literal number 8 wherever it's used. This means [rsi + Player.health] and [rsi + 8] produce byte-identical machine code; the name only helps the human reader and protects against typos if the layout changes.

Arrays of Structures and Nested Access

An array of structures is addressed the same way as an array of primitives, except the 'element size' is the struct's total size (available as StructName_size after struc/endstruc), so element i starts at base + i*StructName_size, and a field within that element is reached by adding the field's own offset on top: base + i*StructName_size + StructName.field. Nested structures (a struct containing another struct as a field, as with Player.pos above) simply chain offsets further — the total offset is the sum of every level's offset, computed once at assembly time and baked into the instruction as a single constant displacement.

🏏

Cricket analogy: Finding player 5's strike rate on a scorecard that lists five stats per player requires multiplying player number by five stats and adding the strike-rate column offset, exactly like base + i*size + field.

Nothing prevents assembling code against a stale or mismatched struct definition — if one module was assembled with an old Player layout (say, before a field was added) and linked against object code compiled with the new layout, every field access computes a wrong-but-plausible offset. This produces subtly corrupted data rather than a link error, since the linker has no type information to catch the mismatch.

  • A structure is a fixed layout of named byte offsets in contiguous memory; the assembler has no native struct concept beyond what struc/endstruc generate.
  • NASM's struc/endstruc directive computes field offsets automatically in declaration order, exposed as constants like Player.health.
  • Field name constants (e.g. Player.health) are pure assembly-time numbers; using them versus a raw offset produces identical machine code.
  • Reordering fields recalculates every subsequent field's offset, making struct layout changes binary-incompatible across separately assembled modules.
  • Padding and alignment are manual; multi-byte fields inside mixed-size structs need explicit padding to stay aligned, since misalignment is legal on x86-64 but slower.
  • An array of structs is indexed as base + i*StructName_size, with a specific field reached by adding that field's own offset on top.
  • Mismatched struct layouts between separately assembled modules produce silently corrupted data, since the linker has no type information to detect the mismatch.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#AssemblyLanguageStudyNotes#StructuresInAssembly#Structures#Assembly#Declaring#Struc#StudyNotes#SkillVeris#ExamPrep