what it printed on the machine that wrote this lab
sum = 10
exit=0
^ 0+1+2+3+4 == 10, and a[5] happened to be 0 this run, so the wrong answer
LOOKS right. A different layout would give a different sum. Silent bug.
No fault, so `gdb run` exits 0. To SEE the off-by-one, step the loop and
watch `i` cross n:
what it printed on the machine that wrote this lab
-ex 'break 84' -ex run -ex 'print n' -ex 'print i' \
-ex 'print a[i]' -ex continue ./bin/14_off_by_one
Breakpoint 1, main () at 14_off_by_one.c:84
$1 = 5 # n
$2 = 0 # i on first hit
$3 = 0 # a[0]
... (set a breakpoint condition `break 84 if i==n` to stop exactly on
the out-of-bounds iteration, then `print a[i]` shows garbage) ...
(break FILE:LINE, then print / continue, and conditional breakpoints
`break ... if COND` are how you corner an off-by-one without a crash.)
run this
$ valgrind -q ./bin/14_off_by_one
what it printed on the machine that wrote this lab
==NNNNN== Invalid read of size 4
==NNNNN== at 0x........: main (14_off_by_one.c:85)
==NNNNN== Address 0x........ is 0 bytes after a block of size 20 alloc'd
==NNNNN== at 0x........: malloc (vg_replace_malloc.c:...)
==NNNNN== by 0x........: main (14_off_by_one.c:80)
How to read it:
"Invalid read of size 4 ... 0 bytes after a block of size 20" -> a[5] is
immediately past a 20-byte (5-int) block => read one element too far.
CONTRAST WITH BUG #04: there the array was on the STACK and valgrind saw
NOTHING (no stack red zones). Here it's on the HEAP, which valgrind DOES
guard, so memcheck catches it. Same off-by-one, different memory => the
tool that works differs. (ASan catches BOTH stack and heap.)
what it printed on the machine that wrote this lab
==NNNNN==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x....
READ of size 4 at 0x.... thread T0
#0 0x... in main .../14_off_by_one.c:85
0x.... is located 0 bytes after 20-byte region ... allocated by ... main:80
ROOT CAUSE vs CRASH SITE:
No crash. The bad READ is at line 85 (sum += a[i]); the ROOT CAUSE is the
loop bound at line 84 (`<=` should be `<`). The fix is the comparison,
not the read.
The array has n=5 elements (valid 0..4) but the sum loop
uses `i <= n`, so it reads a[5] — one past the end. Classic
off-by-one: `<=` where it should be `<`. Buggy line:
`for (int i = 0; i <= n; i++)`.
symptom
SILENT: a[5] is whatever lies just past the block, so `sum`
comes out wrong but the program exits 0. The answer is
garbage that can change run to run. (Here it's a HEAP array,
which matters for tooling — see STEP 3 vs bug #04.)
the fix
Use a strict bound: `for (int i = 0; i < n; i++)`. Off-by-one bugs almost
always come down to `<=` vs `<` or `n` vs `n-1` — audit loop bounds first.
take it further
Change `i <= n` to `i < n`: valgrind and ASan go silent, sum is reliably
10. Then move the array to the stack (`int a[5];`) and keep `i <= n`:
valgrind goes quiet again (bug #04's lesson) but ASan still fires.
============================================================
read the source · debug/14_off_by_one.c
#include<stdlib.h>#include<stdio.h>intmain(void){int n =5;int*a =malloc(n *sizeof(int));/* 5 ints: valid a[0..4] */for(int i =0; i < n; i++) a[i]= i;int sum =0;for(int i =0; i <= n; i++)/* <-- THE BUG: i<=n reads a[5] */ sum += a[i];/* OOB read when i==n */printf("sum = %d\n", sum);free(a);return0;}
◇
Finish the lab: write the evidence note
A lab is done when you can say what it showed on your machine — not when you have read it.
Which role are you preparing for?
It decides which problems come first, which drills open, which labs you see and how the Learn surface is organised. Change it any time from the switch in the top bar, or press Escape to look around first.