Page 1

Array with the elements 42, 18, 12, 9, 0, -5, 13, -8, 12, 23. Then A[6] == 13 Friday, February 17, 2022
Computer Systems
Winter 2022
Stanford University
Computer Science Department
Reading: Course Reader: x86-64 Assembly
Language, Textbook: Chapter 3.1
-
3.4
Lecturer: Chris Gregg
CS 107
Lecture 13:
Assembly Part IV
0x40055d <val_at_index> movslq %esi,%rsi
0x400560 <val_at_index+3> mov (%rdi,%rsi,4),%eax
0x400563 <val_at_index+6> retq

Page 2

Today's Topics


Reading: Chapter 3.7.4-3.7.6, 3.8-3.9


Programs from class: /afs/ir/class/cs107/samples/lect13


Logistics


Midterm requests in by Monday morning


Final day of x86 Assembly Language


Procedures (from last slide-deck)


Local storage on the stack


Local storage in registers


Recursion


Arrays


Structures


Alignment


Function pointers


Assembly wrap-up, and comments on binary bomb assignment.

Page 3

Array Allocation and Access


Arrays in C map in a fairly straightforward way to X86 assembly code, thanks to
the addressing modes available in instructions.


When we perform pointer arithmetic, the assembly code that is produced will have
address computations built into them.


Optimizing compilers are very good at simplifying the address computations (in lab
you will see another optimizing compiler benefit in the form of division — if the
compiler can avoid dividing, it will!). Because of the transformations, compiler-
generated assembly for arrays often doesn't look like what you are expecting.


Consider the following form of a data type T and integer constant N:


The starting location is designated as x
A


The declaration allocates N * sizeof(T) bytes, and gives us an identifier that
we can use as a pointer (but it isn't a pointer!), with a value of
x
A
.
T

A[
N
]

Page 4

Array Allocation and Access


Example:
char A[12];
char *B[8];
int C[6];
double *D[5]
Array

Element Size

Total Size

Start address

Element i
A
1

12
x
A
x
A
+ i
B
8

64
x
B
x
B
+ 8i
C
4

24
x
C
x
C
+ 4i
D
8

40
x
D
x
D
+ 8i


The memory referencing operations in x86-64 are designed to simplify array
access. Suppose we wanted to access
C[3]
above. If the address of C is in
register
%rdx
, and
3
is in register
%rcx


The following copies C[3] into %eax,
movl (%rdx,%rcx,4), %eax

Page 5

Pointer Arithmetic


C allows arithmetic on pointers, where the computed value is calculated according
to the size of the data type referenced by the pointer.


The array reference A[i] is identical to *(A+i)


Example: if the address of array E is in %rdx, and the integer index, i, is in %rcx,
the following are some expressions involving E:
Expression

Type

Value
Assembly Code
E
int *

x
E
movq %rdx, %rax
E[0]
int
int
M[x
E
]
movl (%rdx), %eax
E[i]
int

M[x
E
+4i]
movl (%rdx,%rcx,4) %eax
&E[2]
int *

x
E
+8
leaq 8(%rdx), %rax
E+i
-1
int *

x
E
+4i
-4
leaq
-
4(%rdx,%rcx,4), %rax
*(E+i
-
3)
int

M[x
E
+4i
-12]
movl
-
12(%rdx,%rcx,4) %eax
&E[i]
-E
long

i
movq %rcx,%rax

Page 6

Pointer Arithmetic


Practice: x
S
is the address of a
short
integer array, S, stored in
%rdx
, and a long
integer index,
i
, is stored in register
%rcx
.


For each of the following expressions, give its type, a formula for its value, and an
assembly-code implementation. The result should be stored in
%rax
if it is a
pointer, and the result should be in register
%ax
if it has a data type
short
.
Expression

Type

Value
Assembly Code
S+1
S[3]
&S[i]
S[4*i+1]
S+i
-5

Page 7

Pointer Arithmetic


Practice: x
S
is the address of a
short
integer array, S, stored in
%rdx
, and a long
integer index,
i
, is stored in register
%rcx
.


For each of the following expressions, give its type, a formula for its value, and an
assembly-code implementation. The result should be stored in
%rax
if it is a
pointer, and the result should be in register
%ax
if it has a data type
short
.
Expression

Type

Value
Assembly Code
S+1

short *

x
S
+ 2

leaq 2(%rdx),%rax
S[3]

short

M[x
S
+ 6]

movw 6(%rdx),%ax
&S[i]

short *

x
S
+ 2i

leaq (%rdx,%rcx,2),%rax
S[4*i+1]

short

M[x
S
+ 8i + 2]

movw 2(%rdx,%rcx,8),%ax
S+i
-5
short *

x
S
+ 2i
-
10

leaq
-
10(%rdx,%rcx,2),%rax

Page 8

Structures


The C struct declaration is used to group objects of different types into a single
unit.


Each "field" is referenced by a name, and can be accessed using dot (.) or (if
there is a pointer to the struct) arrow (
->
) notation.


Structures are kept in contiguous memory


A pointer to a struct is to its first byte, and the compiler maintains the byte offset
information for each field.


In assembly, the references to the fields are via the byte offsets.

Page 9

Structures


Example:
struct rec {
int i;
int j;
int a[2];
int *p;
};


This structure has four fields: two 4-byte values of type int, a
two
-element array of type int, and an 8-byte int pointer, for a
total of 24 bytes:
Offset
0

4

8

16 24
Contents
i

j

a[0] a[1]
p


The numbers along the top of the diagram are the byte offsets of the fields from
the beginning of the structure.


Note that the array is embedded in the structure.


To access the fields, the compiler generates code that adds the field offset to the
address of the structure.

Page 10

Structures


Example:
struct rec {
int i;
int j;
int a[2];
int *p;
};


This structure has four fields: two 4-byte values of type int, a
two
-element array of type int, and an 8-byte int pointer, for a
total of 24 bytes:
Offset
0

4

8

16 24
Contents
i

j

a[0] a[1]
p


Example: Variable r of type struct rec * is in register %rdi. The following
copies element
r->i
to element
r->j
:
movl (%rdi), %eax // get r->i
movl %eax, 4(%rdi) // store in r->j


The offset of i is 0, so i's field is %rdi. The offset of j is 4, so the offset of 4 is
added to the address of
%rdi
to store into
j
.

Page 11

Structures


Example:
struct rec {
int i;
int j;
int a[2];
int *p;
};


This structure has four fields: two 4-byte values of type int, a
two
-element array of type int, and an 8-byte int pointer, for a
total of 24 bytes:
Offset
0

4

8

16 24
Contents
i

j

a[0] a[1]
p


We can generate a pointer to a field by adding the field's offset to the struct
address. To generate
&(r->a[1])
we add offset
8 + 4 = 12
. For a pointer
r
in register
%rdi
and long
int
variable
i
in
%rsi
, we can generate the pointer
value
&(r->a[i])
with one instruction:
leaq 8(%rdi,%rsi,4), %rax // set %rax to &r->a[i]

Page 12

Structures


Example:
struct rec {
int i;
int j;
int a[2];
int *p;
};


This structure has four fields: two 4-byte values of type int, a
two
-element array of type int, and an 8-byte int pointer, for a
total of 24 bytes:
Offset
0

4

8

16 24
Contents
i

j

a[0] a[1]
p


The following code implements r->p = &r->a[r->i + r->j];
// r in %rdi
movl 4(%rdi),%eax // get r
-
>j
addl (%rdi),%eax // add r
-
>i
cltq // extend %eax to 8 bytes, %rax
leaq 8(%rdi,%rax,4), %rax // compute &r
-
>a[r
-
>i + r
-
>j]
movq %rax, 16(%rdi) // store in r
-
>p

Page 13

Structures


Example:
struct rec {
int i;
int j;
int a[2];
int *p;
};


This structure has four fields: two 4-byte values of type int, a
two
-element array of type int, and an 8-byte int pointer, for a
total of 24 bytes:
Offset
0

4

8

16 24
Contents
i

j

a[0] a[1]
p


Notice that all struct manipulation is handled at compile time, and the machine
code doesn't contain any information about the field declarations or the names of
the fields.


The compiler does all the work, keeping track as it produces the assembly code.


BTW, if you're curious about how the compiler actually does the transformation
from C to assembly, take a compilers class, e.g., CS143.

Page 14

Data Alignment


Computer systems often put restrictions on the allowable addresses for primitive
data types, requiring that the address for some objects must be a multiple of
some value
K
(normally 2, 4, or 8).


These alignment restrictions simplify the design of the hardware.


For example, suppose that a processor always fetches 8 bytes from the memory
system, and an address must be a multiple of 8. If we can guarantee that any
double

will be aligned to have its address as a multiple of 8, then we can read or
write the values with a single memory access.


For x86-64, Intel recommends the following alignments for best performance:
K

Types
1

char
2

short
4

int, float
8

long, double, char *

Page 15

Data Alignment


The compiler enforces alignment by making sure that every data type is organized
in such a way that every field within the struct satisfies the alignment restrictions.


For example, let's look at the following struct:
struct S1 {
int i;
char c;
int j;
};


If the compiler used a minimal allocation:


This would make it impossible to align fields i (offset 0) and j (offset 5). Instead,
the compiler inserts a 3-byte gap between fields
c
and
j
:
Offset
0

4

5

9
Contents
i

c

j
Offset
0

4

5

8

12
Contents
i

c

j


So, don't be surprised if your structs have a sizeof() that is larger than you expect!

Page 16

Function Pointers in Assembly


Let's look at the following code:
void *gfind_max(void *arr, int n, size_t elemsz,
int (*compar)(const void *, const void *))
{
void *pmax = arr;
for (int i = 1; i < n; i++) {
void *ith = (char *)arr + i*elemsz;
if (compar(ith, pmax) > 0)
pmax = ith;
}
return pmax;
}
int cmp_alpha(const void *p, const void *q)
{
const char *first = *(const char **)p;
const char *second = *(const char **)q;
return strcmp(first, second);
}
int main(int argc, char *argv[])
{
char **pmax = gfind_max(argv+1, argc
-
1, sizeof(argv[0]), cmp_alpha);
printf("max = %s
\
n", *pmax);
return 0;
}

Page 17

Function Pointers in Assembly


Let's look at the following code:
void *gfind_max(void *arr, int n, size_t elemsz,
int (*compar)(const void *, const void *))
{
void *pmax = arr;
for (int i = 1; i < n; i++) {
void *ith = (char *)arr + i*elemsz;
if (compar(ith, pmax) > 0)
pmax = ith;
}
return pmax;
}
int cmp_alpha(const void *p, const void *q)
{
const char *first = *(const char **)p;
const char *second = *(const char **)q;
return strcmp(first, second);
}
int main(int argc, char *argv[])
{
char **pmax = gfind_max(argv+1, argc
-
1, sizeof(argv[0]), cmp_alpha);
printf("max = %s
\
n", *pmax);
return 0;
}


Because compar is a function
pointer, the compiler calls the
function via the address that is
in the compar variable.


Let's take a look at this in gdb.

Page 18

References and Advanced Reading


References:


Stanford guide to x86-64: https://web.stanford.edu/class/cs107/guide/x86-
64.html


CS107 one-page of x86-64:
https://web.stanford.edu/class/cs107/resources/onepage_x86-64.pdf


gdbtui: https://beej.us/guide/bggdb/


More gdbtui: https://sourceware.org/gdb/onlinedocs/gdb/TUI.html


Compiler explorer: https://gcc.godbolt.org


Advanced Reading:


Stack frame layout on x86-64: https://eli.thegreenplace.net/2011/09/06/stack-
frame
-
layout
-on-x86-64


x86-64 Intel Software Developer manual:
https://software.intel.com/sites/default/files/managed/39/c5/325462-sdm-
vol
-1-
2abcd-3abcd.pdf


history of x86 instructions: https://en.wikipedia.org/wiki/X86_instruction_listings


x86-64 Wikipedia: https://en.wikipedia.org/wiki/X86-64









Page 19

Extra Slides
Extra Slides