9.9. Structs in Assembly
A struct is another way to create a collection of data types in C. Unlike arrays, structs enable different data types to be grouped together. C stores structs like single dimension arrays, where data elements (fields) are stored contiguously.
Let’s revisit the studentT
struct from Chapter 1:
struct studentT {
char name[64];
int age;
int grad_yr;
float gpa;
};
struct studentT student;
Figure 1 shows how the student struct is laid out in memory. Each ai denotes an offset in memory.

Each field is stored contiguously next to each other in memory in the order in which they are declared. In [structArray6]
the age
field is allocated at the memory location directly after the name
field (at byte offset a64), and is
followed by the grad_yr
(byte offset a68) and gpa
(byte offset a72) fields. This organization enables memory-efficient
access to the fields of the struct.
To understand how the compiler generates assembly code to work with structs, consider the function initStudent()
:
void initStudent(struct studentT *s, char *nm, int ag, int gr, float g) {
strcpy(s->name, nm);
s->grad_yr = gr;
s->age = ag;
s->gpa = g;
}
The initStudent()
function uses the base address of a studentT
struct as its first parameter, and desired values
for each field as its remaining parameters. The listing below depicts
this function in assembly.
Dump of assembler code for function initStudent: 0x7f4 <+0>: stp x29, x30, [sp, #-48]! // sp-=48; store x29 and x30 at sp and sp+4 0x7f8 <+4>: mov x29, sp // x29 = sp (frame pointer = stack pointer) 0x7fc <+8>: str x0, [x29, #40] // store s at x29 + 40 0x800 <+12>: str x1, [x29, #32] // store nm at x29 + 32 0x804 <+16>: str w2, [x29, #28] // store ag at x29 + 28 0x808 <+20>: str w3, [x29, #24] // store gr at x29 + 24 0x80c <+24>: str s0, [x29, #20] // store g at x29 + 20 0x810 <+28>: ldr x0, [x29, #40] // x0 = s 0x814 <+32>: ldr x1, [x29, #32] // x1 = nm 0x818 <+36>: bl 0x6e0 <strcpy@plt> // call strcpy(s, nm) (s->name) 0x81c <+40>: ldr x0, [x29, #40] // x0 = s 0x820 <+44>: ldr w1, [x29, #24] // w1 = gr 0x824 <+48>: str w1, [x0, #68] // store gr at location (s + 68) (s->grad_yr) 0x828 <+52>: ldr x0, [x29, #40] // x0 = s 0x82c <+56>: ldr w1, [x29, #28] // w1 = ag 0x830 <+60>: str w1, [x0, #64] // store ag at location (s + 64) (s->age) 0x834 <+64>: ldr x0, [x29, #40] // x0 = s 0x838 <+68>: ldr s0, [x29, #20] // s0 = g 0x83c <+72>: str s0, [x0, #72] // store g at location (s + 72) (s->gpa) 0x844 <+80>: ldp x29, x30, [sp], #48 // x29 = sp, x30 = sp+4, sp += 48 0x848 <+84>: ret // return (void)
Being mindful of the byte offsets of each field is key to understanding this code. A few things to keep in mind:
-
the
strcpy()
call takes the base address of thename
field of structs
and the address of arraynm
as its two arguments. Recall that sincename
is the first field in thestudentT
struct, the address ofs
is synonymous with the address ofs→name
.
0x7fc <+8>: str x0, [x29, #40] // store s at x29 + 40 0x800 <+12>: str x1, [x29, #32] // store nm at x29 + 32 0x804 <+16>: str w2, [x29, #28] // store ag at x29 + 28 0x808 <+20>: str w3, [x29, #24] // store gr at x29 + 24 0x80c <+24>: str s0, [x29, #20] // store g at x29 + 20 0x810 <+28>: ldr x0, [x29, #40] // x0 = s 0x814 <+32>: ldr x1, [x29, #32] // x1 = nm 0x818 <+36>: bl 0x6e0 <strcpy@plt> // call strcpy(s, nm) (s->name)
-
The above code snippet contains an undiscussed register (
s0
). Thes0
register is an example of a register reserved for floating point values. -
The next part of the code (instructions
<initStudent+40>
thru<initStudent+48>
) places the value of thegr
parameter at an offset of68
from the start ofs
. Revisiting the memory layout of the struct in [structArray6] shows that this address corresponds tos→grad_yr
.
0x81c <+40>: ldr x0, [x29, #40] // x0 = s 0x820 <+44>: ldr w1, [x29, #24] // w1 = gr 0x824 <+48>: str w1, [x0, #68] // store gr at location (s + 68) (s->grad_yr)
-
The next section of code (instructions
<initStudent+52>
thru<initStudent+60>
) copies theag
parameter to thes→age
field of the struct, which is located at an offset of64
bytes from the address ofs
.
0x828 <+52>: ldr x0, [x29, #40] // x0 = s 0x82c <+56>: ldr w1, [x29, #28] // w1 = ag 0x830 <+60>: str w1, [x0, #64] // store ag at location (s + 64) (s->age)
Lastly, the g
parameter value is copied to the s→gpa
field (byte offset 72
) of the struct. Notice the use of
the s0
register since the data contained at location x29 + 20
is single precision floating point:
0x834 <+64>: ldr x0, [x29, #40] // x0 = s 0x838 <+68>: ldr s0, [x29, #20] // s0 = g 0x83c <+72>: str s0, [x0, #72] // store g at location (s + 72) (s->gpa)
9.9.1. Data Alignment and Structs
Consider the following modified declaration of our studentT
struct:
struct studentTM {
char name[63]; //updated to 63 instead of 64
int age;
int grad_yr;
float gpa;
};
struct studentTM student2;
The size of the name
field of the struct is modified to be 63 bytes, instead of the original 64 bytes. Consider how this affects the way the struct is
laid out in memory. It may be tempting to visualize it like so:

In this depiction, the age
field occurs in the byte immediately following the name
field. But this is incorrect. Figure 3 depicts the actual
layout of the struct in memory:

A64’s alignment policy requires that 4-byte data types (i.e, int
) reside at addresses that are a multiple of 4, while 64-bit data types (long
, double
and pointer data) to reside at addresses that
are a multiple of 8. For structs, the compiler adds empty bytes as "padding" between fields to ensure that each field satisfies its alignment requirements. For example, in the struct declared above, the compiler adds a byte of padding at byte a63 to ensure that the age
field starts at an address that is at a multiple of 4.
Values aligned properly in memory can be read or written in a single operation, enabling greater efficiency.
Consider what happens when the struct is defined as the following:
struct studentTM {
int age;
int grad_yr;
float gpa;
char name[63];
};
struct studentTM student3;
Moving the name
array to the end of the struct ensures that age
, grad_yr
and gpa
are 4-byte aligned. Most compilers will remove the filler byte at the end of the struct.
However, if the struct is ever used in the context of an array (e.g., struct studentTM courseSection[20];
) the compiler will once again add the filler byte as padding between each
struct element in the array to ensure that alignment requirements are properly met.