How does a compiler translate traversing a linked list (following r = r->n until NULL) into assembly?
As a while (r != NULL) loop: load fields by their struct offsets, then load the next pointer and test it against itself to detect NULL.
* movq 16(%rdi),%rdi advances to the next node; testq %rdi,%rdi ; jne loops until the NULL pointer. *
A linked list walk is just a loop whose update step is "follow a pointer." Each iteration reads fields at fixed offsets, then replaces the current pointer with the node's next field:
struct rec { int i; int a[3]; struct rec *n; }; // n at offset 16
void set_val(struct rec *r, int val) {
while (r != NULL) {
int i = r->i;
r->a[i] = val;
r = r->n;
}
}
Assembly (guarded do-while, as compilers emit while-loops):
set_val:
jmp .L7 # jump to the guard test first
.L5:
movslq (%rdi), %rax # rax = r->i (offset 0)
movl %esi, 4(%rdi,%rax,4) # r->a[i] = val (a at offset 4)
movq 16(%rdi), %rdi # r = r->n (next at offset 16)
.L7:
testq %rdi, %rdi # is r NULL?
jne .L5 # if not, loop again
rep ret
What to recognize: the movq 16(%rdi), %rdi that overwrites the base pointer with a value loaded from that struct is the signature of "advance to next node," and testq %reg,%reg ; jne is the NULL-terminated loop condition.
Go deeper:
Linked list (Wikipedia) — the linked list data structure.