Skip to main content

Structured Data (Structures, Arrays and String Operations)

Arrays

We can consider an array as a sequence of elements of the same type, placed contiguously in memory. You might have noticed something similar in previous labs when declaring static character strings in the .data section.

Declaring an Array

In general, declared static data can be initialized or uninitialized. Differentiation is made both by providing an initial value for initialized data and by the NASM syntax used.

For example, to declare an array of 100 words initialized with the value 42, we will use the construction:

section .data
my_vect: times 100 dw 42

On the other hand, if we want to declare an uninitialized array of 20 double-word elements, we use instructions from the "res" family as follows:

section .bss
my_vect: resd 20

Arrays of Structures

Often, we'll need arrays that contain elements larger than a double word. To achieve this, we'll combine the two concepts presented earlier and use arrays of structures. Of course, string operation instructions will not work, so we'll have to resort to the classic method of accessing elements: explicit memory addressing.

For the example in this section, we create a structure representing a point in a 2D space.

struc point
.x: resd 1
.y: resd 1
endstruc

Declaring an Array of Structures

Since NASM doesn't support any mechanism to explicitly declare an array of structures, we'll need to effectively declare a data section to accommodate our array.

Suppose we want a zero-initialized array of 100 elements of the structure type point (which is 8 bytes in size), we need to allocate 100 * 8 (= 800) bytes.

We obtain:

section .data
point_array: times 800 db 0

In addition, NASM provides an alternative to manually calculating the size of a structure by automatically generating the macro <structure name>_size. Thus, the previous example can become:

section .data
point_array: times point_size * 100 db 0

If we want to declare an uninitialized array of structures, we can use:

section .bss
point_array: resb point_size * 100

Traversing an Array of Structures

As mentioned before, to access a field of an element in an array, we need to use normal addressing (particularly "based-indexed with scale" addressing). The formula to find the address of the element is base_of_array + i * size_of_struct.

Assuming we have the start address of the array in the ebx register and the index of the element we want to access in the eax register, the following example demonstrates printing the value of the y field of this element.

mov ebx, point_array                            ; Move the start address of the array into ebx
mov eax, 13 ; Assume we want the 14th element
mov edx, [ebx + point_size * eax + point.y] ; Calculate the address of the desired field between []
; and then transfer the value from that address
; into the edx register

PRINTF32 `%u\n\x0`, edx

We traverse the array, having the current index in the eax register at each iteration. We can print the values from both fields of each element in the array with the following program:

struc   point
.x: resd 1
.y: resd 1
endstruc

section .data
point_array: times point_size * 100 db 0

section .text
global CMAIN

CMAIN:
push ebp
mov ebp, esp

xor edx, edx
xor eax, eax

label:
mov edx, [point_array + point_size * eax + point.x] ; access x member
PRINTF32 `%u\n\x0`, edx

mov edx, [point_array + point_size * eax + point.y] ; access y member
PRINTF32 `%u\n\x0`, edx

inc eax ; increment input index
cmp eax, 100
jl label

leave
ret