Arrays and Structs
Arrays
1D Arrays
In C: An array is a sequence of elements where each element is just a variable. All elements will have the same type, called the element type. The number of elements is fixed at the type of the array creation.
- Access through an integer index: 0 is the first element and 1 is the second.
- The subscript operator
a[i]refers to the element at indexiin arraya.
The program must be able to determine the array element's address. For an array, the program just needs to know the base element (the address of the first element). After that, all the elements are at a fixed offset from the base address. Thus, the address of any element can be computed from the base address.
- Address of an element = base address + offset.
At the machiene level, addresses correspond to bytes, so to compute the correct element offset, the array index must be multiplied by the element size in bytes.
Example C program
#include <stdio.h>
int main(void) {
int arr[3] = { 1, 2, 3 };
printf("%p\n%p\n%p\n", &arr[0], &arr[1], &arr[2]);
return 0;
}
Output:
$ gcc arrptr.c
$ ./a.out
0x7ffc822662fc
0x7ffc82266300
0x7ffc82266304
- Note that
sizeof(int)= 4 and the array elements have addresses which differ by 4.
In x86-64 Assembly, arrays are straightforward if the elements are 1, 2, 4, or 8 bytes in size as it allows indexed/scaled addressing.
- Any general purpose reigster can store an address (base address or element pointer) or be used as an index.
Pointer Arithmetic
The array/pointer duality: If a is the name fo the array, a can be considered to be the pointer to the first element of the array (the base address).
Pointer identities
a[i] is the same thing as *(a+i).
- Implies that
&a[i]means the same thing as(a + i)
In general,
ppoints to an array elementp+ipoints to the elementipositions past the oneppoints top-ipoints to the elmenetipositions before the oneppoints to
Pointer difference
Say p and q are pointers, i is an integer, and p+i=q, then it is also true that q-p=i.
ptrdiff_t is a signed type that represents the difference between pointer values.
- It must be a signed type since the difference could be negative.
- The type of the pointer affects the result of pointer difference
- C langauges standared only guarantees pointer difference is meaningful when comparing pointeres from the same chunk of memory (array ,
malloc'ed buffer, etc.)
Accessing array elements Example
C
We want to write a C function to compute the sum of an array of uint32_t elements:
C function prototype: uint32_t sum_elts(uint32_t arr[], unsigned len);
- Recall that in C, an array parameter is really a pointer to the first element of the argument array
First C approach (indexed version):
uint32_t sum_elts(uint32_t arr[], unsigned len) {
uint32_t sum = 0;
for (unsigned i = 0; i < len; i++) {
sum += arr[i];
}
return sum; }
Second C approach (element pointer version):
uint32_t sum_elts(uint32_t arr[], unsigned len) {
uint32_t *p = arr, *end = arr + len;
uint32_t sum = 0;
while (p < end) {
sum += *p;
p++;
}
return sum;
}
Assembly First assembly approach (indexed version):
sum_elts:
movl $0, %eax // keeps track of sum
movl $0, %r10d // keeps track of current index
.LsumLoop:
cmpl %esi, %r10d // is index < n
jae .sumLoopDone
addl (%rdi,%r10,4), %eax
incl %r10d
jmp .LsumLoop
.LsumLoopDone:
ret
- Initially,
%rdiis the base address, and%esiis the number of elements. movl $0, %eaxinitializessumin%eaxmovl $0, %r10dinitializes%r10dasindex.cmpl %esi, %r10dsees ifindex < njae .LsumLoopDonesays if it is not, we are done with the loop.
addl (%rdi,%r10,4), %eaxaddsarr[indextosum. Note that%rdiis the base element,%r10is the index, and the4is the scaling factor.incl %r10dincrements the indexjmp .LsumLoopcontinues the loopretends the program andsumwill be%eax
Second assembly apporach (element pointer):
sum_elts:
movl $0, %eax // keeps track of sum
leaq (%rdi,%rsi,4), %r10 // sets %r10 as address past the last element
.LsumLoop:
cmpq %r10, %rdi // has %rdi past the last element
jae .LsumLoopDone
addl (%rdi), %eax
addq $4, %rdi // increment the pointer
jmp .LsumLoop
.LsumLoopDone:
ret
addq $4, %rdiadvances to next element.
So what approach should I use?
- If element size is 1, 2, 4, or 8, then either approach is fine
- Otherwise, the element pointer approach may be preferable (since indexed/scaled addressing can’t be used as easily)
Multidimensional Arrays
Multidimensional arrays in C are laid out in row-major order
- Example 2D array: int grid[3][4] would have 3 rows and 4 columns.
In memory, the elements within each row are sequential in memory. Smaller row indexes are in the lower addresses.
Accessing elements of multidimensional array: A typical loop to iterate over elements of a 2D array:
for (int i = 0; i < NROWS; i++) {
for (int j = 0; j < NCOLS; j++) {
/* do something with arr[i][j] */
}
}
Strategy to access elements:
- Each iteration of outer loop computes address of first element of row (
arr[i][0]) - The inner loop can then treat
arr[i]as a one-dimensional array - Various optimizations are possible. For example, loop above accesses elements sequentially in memory, could treat the 2-D array as a 1-D array with
NROWS × NCOLS elements
Structs
A struct (a.k.a. “record”) is a heterogenous data type consisting of an arbitrary number of fields with arbitrary types.
To access a filed within a struct instance, you need to know
- the base address of the struct instance
- the offset of the field being acessed.
Accessing a field is similar to accessing an array element, except that each field has a specific constant offset known at compile time
Field Alignment
#include <stdio.h>
struct Player {
int x, y;
char symbol;
short health;
};
int main(void) {
printf("%lu\n", sizeof(struct Player));
return 0;
}
Would print out
$ gcc structlayout.c
$ ./a.out
12
- It is 12 because the
ints are both 4 bytes.charis 1 byte, and thenshortis 2 bytes, but the compiler must ensure that the memory accesses are properly alligned.- e.g., a 4 byte
intvariable must have its sotrage allocated at an address that is a multiple of 4, so the compiler may add padding bytes.
- e.g., a 4 byte
The compiler may also add padding at the end of the struct to guarantee alignment of all fields.
- For example, if the struct type has fileds requiring 8 bytes, the struct must be a multiple of 8.
Accessing Structs
Assuming you have the base address of a struct instance in a register, the field ias at a fixed offset from the base address. Specify the offset when making the memory reference.
In C:
struct Player {
int x, // offset 0
y; // offset 4 because x takes up 4 bytes
char symbol;
short health;
};
void move_player(struct Player *p, int dx, int dy) {
p->x += dx;
p->y += dy;
}
In assembly:
/* Note that first three arguments are in %rdi, %rsi, and %rdx */
#define PLAYER_X_OFFSET 0
#define PLAYER_Y_OFFSET 4
move_player:
addl %esi, PLAYER_X_OFFSET(%rdi) /* p->x += dx */
addl %edx, PLAYER_Y_OFFSET(%rdi) /* p->y += dy */
ret
- Using
#defineto be an easy reference.
Linked from Computer System Fundamentals