Array indexing and struct field access become address calculations followed by memory access.

Learning Question

What do array indexing and struct field access become below the C syntax?

C lets the programmer write:

values[i]
point.x

These expressions look different at the source level.

At the machine level, both require the program to find a memory address.

The first mental model is:

Arrays and structs are accessed by calculating where the desired element or field is located, then reading or writing memory at that address.

Base Address Plus Offset

The common lower-level pattern is:

target address = base address + offset

The base address identifies where a larger object begins.

The offset identifies where the desired part of that object is located inside it.

For an array, the offset depends on the element index and the element size.

For a struct, the offset depends on the field’s fixed position inside the struct layout.

This is the bridge between C syntax and memory access.

Array Elements Are Laid Out Consecutively

An array stores elements of the same type in consecutive memory locations.

For example:

int values[4];

declares an array with four int elements:

values[0]
values[1]
values[2]
values[3]

If int is 4 bytes on the target platform, then the elements are laid out conceptually like this:

ElementOffset From Start of values
values[0]0 bytes
values[1]4 bytes
values[2]8 bytes
values[3]12 bytes

The exact address of the array may vary at runtime.

The offsets inside the array follow from the element type and layout.

Array Indexing Is Scaled Address Calculation

The expression:

values[i]

means:

access the element at index i

At the lower level, that requires an address calculation:

address of values[i] = base address of values + i * sizeof(values[0])

If values is an int array and sizeof(int) is 4, then:

address of values[i] = base address of values + i * 4

Then the program reads or writes memory at that computed address.

This explains why array indexing is closely related to pointer arithmetic.

The index is not only a number.

It is a number scaled by the size of the element type.

Example: Reading an Array Element

Consider:

int read_at(int *values, int i)
{
    return values[i];
}

At the C level, values[i] reads the element at index i.

At the instruction level, the generated code must:

  1. get the base address stored in values
  2. get the index value i
  3. scale the index by the element size
  4. add the scaled offset to the base address
  5. read the int at the computed address

A simplified x86-64-shaped memory operand might look like:

(%rax,%rdx,4)

Conceptually:

memory at address: %rax + %rdx * 4

If %rax holds the base address and %rdx holds the index, the scale 4 corresponds to the size of an int on that platform.

Do not focus on memorizing this syntax yet.

Focus on the shape:

base + index * element_size

Array Names and Pointers

In many expressions, an array expression is converted to a pointer to its first element.

For example:

values[i]

is closely related to:

*(values + i)

This does not mean an array object and a pointer object are the same thing.

An array object contains elements.

A pointer value contains an address.

But array indexing often uses pointer-like address calculation.

The key point is:

Array indexing uses the base address of the array’s elements plus a scaled offset.

C Does Not Automatically Store Array Bounds in a Pointer

The expression:

values[i]

does not automatically check whether i is within the array bounds in ordinary C.

The generated address calculation can still compute an address for an out-of-range index.

That address may refer to memory that is not part of the array.

Using it is undefined behavior in C.

This is another reason the source-level boundary matters:

The C program must ensure that the index is valid. The ordinary pointer/address calculation does not carry the array length with it.

Struct Fields Have Fixed Offsets

A struct groups fields into one object.

For example:

struct Point {
    int x;
    int y;
};

If a struct Point object is stored in memory, its fields are placed at fixed offsets from the start of the struct object.

Conceptually:

FieldOffset From Start of struct Point
x0 bytes
y4 bytes

This table is plausible for two 4-byte int fields on a common platform.

Actual struct layout is determined by the C implementation’s size and alignment rules.

The important idea is that field access is offset-based.

Struct Field Access Is Base Plus Field Offset

The expression:

point.y

means:

access the y field inside the struct object point

At the lower level, that means:

address of point.y = base address of point + offset of field y

If y is at offset 4, then accessing point.y means reading or writing memory 4 bytes after the start of point.

For a pointer to a struct:

p->y

the program first uses the address stored in p as the base address.

Then it adds the field offset.

So:

p->y

is conceptually:

memory at address: pointer value p + offset of field y

Struct Padding and Alignment

Struct fields are not always packed with no gaps.

The compiler may insert padding bytes between fields or after the final field to satisfy alignment requirements.

For example:

struct Mixed {
    char tag;
    int value;
};

It may be tempting to assume value starts 1 byte after tag.

On many platforms, value will instead be placed at an offset such as 4 so the int is properly aligned.

The exact offsets are implementation-dependent.

The durable rule is:

Struct field access uses offsets chosen by the struct layout rules for the target platform.

Arrays of Structs Combine Both Rules

Consider:

struct Point points[10];
int y = points[i].y;

This combines array indexing and struct field access.

The lower-level address calculation has two parts:

base address of points
+ i * sizeof(struct Point)
+ offset of field y

The array index chooses which struct element.

The field offset chooses which field inside that struct element.

This is why arrays and structs belong together in the same mental model:

Both are layout rules that become address calculations.

Source Shape Versus Memory Shape

C source code gives names and structure:

points[i].y

The generated instructions must compute where the value lives:

start of points
plus scaled array index
plus field offset
then read or write memory

The source expression is compact because C knows the types.

The lower-level form is concrete because the machine needs addresses.

This is the central distinction:

C expresses selection by name and index. Machine instructions implement selection by address calculation.

What This Chapter Does Not Explain Yet

This chapter explains arrays and structs as address calculations.

It does not yet fully explain:

  • every x86-64 addressing mode
  • multidimensional arrays in detail
  • flexible array members
  • bit-fields
  • packed structs
  • strict aliasing
  • cache behavior
  • dynamic allocation of arrays or structs

Those topics are useful later, but the first durable idea is simpler:

Find the base address, calculate the offset, then access memory.

Core Mental Model

Keep these boundaries separate:

  • An array contains consecutive elements of the same type.
  • Array indexing uses base + index * element_size.
  • A struct contains fields at fixed offsets chosen by layout and alignment rules.
  • Struct field access uses base + field_offset.
  • Pointer-to-struct access uses the pointer value as the base address.
  • Arrays of structs combine scaled element selection with field offsets.
  • C source uses names, types, and indexes; generated instructions use addresses and offsets.

When reading C expressions such as values[i], point.y, or points[i].y, ask:

What is the base address, and what offset must be added to reach the selected object?

Final Summary

Arrays and structs make C’s memory layout visible.

An array access calculates an element address from a base address and a scaled index.

A struct field access calculates a field address from a base address and a fixed field offset.

Assembly helps reveal these calculations beneath C’s compact indexing and field-access syntax.