Spatial safety: prove the entire access fits
Check offsets, access widths, alignment, and size arithmetic instead of treating a non-null pointer as a valid buffer.
On this page
The question for today
What you’ll understand
- Check a byte access with a half-open allocation range.
- Avoid overflow in bounds and allocation-size calculations.
- Separate spatial validity from lifetime, initialization, and semantic correctness.
A pointer can designate a live allocation and still access the wrong bytes. Spatial memory safety concerns whether an operation stays within the region it is permitted to access. The important unit is the entire operation: a four-byte load needs four valid bytes, not just a valid starting address. Make length, element width, and arithmetic assumptions visible before deriving an address.
Check the whole interval
Model an allocation as N accessible bytes indexed from zero through N−1. A nonempty access starting at offset o and using w bytes fits only if o is nonnegative, o is no greater than N, and w is no greater than N−o. For example, a four-byte access starting at byte 14 of a sixteen-byte buffer exceeds its end even though byte 14 itself exists. The check must cover the entire range. Real pointer APIs also specify which operations may form or use an end pointer without dereferencing it.
An index in elements and an offset in bytes are different quantities. A packed array of n elements of size s has an ideal payload of n times s, but constructing that product can overflow a finite integer type. Checked multiplication must establish that the requested size is representable before allocation. Similarly, computing o+w before checking an upper bound can wrap and make an invalid range appear small. A subtraction-based condition avoids that particular addition overflow only after establishing o≤N; it is not permission to ignore all other arithmetic.
An in-range address may still be invalid
Typed access can require alignment, initialized contents, and a representation valid for the type. A live byte buffer containing arbitrary network data is not automatically a valid in-memory instance of a structured language type. Endianness and padding can differ from the wire format, while a boolean or enum may forbid some bit patterns. Prefer explicit decoding with length checks and format validation. Copying bytes into an aligned location may solve alignment while leaving invalid values and incorrect interpretation untouched.
The permitted access region can also be smaller than the surrounding allocation. A slice exposes a particular logical length; spare container capacity is not automatically initialized data available through that slice. Two neighboring allocations do not become one legal array merely because their numerical addresses happen to be contiguous. Pointer provenance and the target API's rules matter. Distinguish a successful hardware load from a justified language-level access, and distinguish that access from the application-level fact that the bytes belong to the right user's record.
Make the checked boundary reusable
A sound parser should validate a region once and pass a constrained view to the next operation, rather than repeatedly reconstructing raw addresses from unchecked counts. Check every external length before a narrowing conversion, enforce resource limits separately from arithmetic representability, and validate nested records against their own boundaries. A declared one-gigabyte payload might fit the integer representation while exceeding the service's allowed request budget. Safe indexing usually gives a defined error or panic when bounds fail, but the surrounding code must still handle that outcome appropriately.
Use the vector lab only as a geometric interval exercise. At angle zero, magnitude two has x projection two; rotating to 180 degrees makes that projection negative. Interpret x as a normalized offset into a hypothetical three-unit buffer and give the access width one unit. Offset two fits exactly through the end, while a negative offset does not. Increasing magnitude to three produces an in-range starting endpoint but no room for a nonempty access. The diagram never constructs or validates pointers. It helps distinguish a boundary point from an entire permitted interval.
Work it through
Reject an access that crosses the end
A live sixteen-byte buffer is accessed at offset 14 with width 4. Another operation requests 300 elements of four bytes using an eight-bit unsigned size representation.
For the first access, subtract only after checking 14≤16. The remaining region has 16−14=2 bytes, which is smaller than the required width 4, so reject the access.
For the allocation-size example, the eight-bit maximum is 255 bytes. The allowed element count is at most floor(255/4)=63; requesting 300 cannot be represented in that model.
Use a suitable checked size type and enforce the real allocator and service limits. Merely widening the integer does not make the original four-byte access at offset 14 valid.
Range containment and representable allocation size are separate checks. Neither establishes initialization, alignment, lifetime, or correct decoding.
The living notebook
Explore the model
Interactive lab · vectors
A boundary point is not a valid access range
At angle 0°, set magnitude 2 and read x=2. For a hypothetical length-3 interval and width-1 access, it fits. Set magnitude 3: x is now the endpoint with no room for width 1. Rotate to 180° to see an invalid negative offset. This geometry does not check pointer semantics.
- x component
- 1.393
- y component
- 0.975
- Magnitude
- 1.7
Playback traces a fixed vector and its projections. Angle and magnitude set the geometry; the moving marker does not change them. Parameter changes pause playback and recompute the model at the current elapsed time. Reset restores both parameters and returns time to zero. Playback starts paused. Adjust either parameter, then choose Play or Step.
Look forAn access requires space for its full width, even when its starting endpoint looks familiar.
Pause & reason
Check your understanding
Your turn
Try it yourself
A packed buffer exposes 40 bytes. Check a six-byte read at offset 34 and a six-byte read at offset 35. Then find the largest number of six-byte elements fitting a 255-byte representable limit.
Take a moment to make a prediction or work it out on paper. Open a hint when you need one.
Give me a hint
Use remaining bytes for each read, then integer division for the element count.
Compare with the solution
At offset 34 there are six bytes left, so the byte interval fits exactly. At offset 35 only five remain, so reject it. floor(255/6)=42 elements fit the size limit, requiring 252 bytes; 43 require 258. The range checks remain necessary rather than sufficient for typed pointer access.
Keep the idea
Take it with you
- Prove containment for the entire byte interval, not just its start.
- Check size arithmetic and conversions before constructing an allocation or address.
- Bounds do not replace lifetime, alignment, initialization, or provenance rules.
Read further
Original sources and deeper explanations to follow at your own pace.
Optional progress, saved in this browser when available. Revisit any lesson at any time.
Lesson not marked complete.