Array Out-of-Bounds Access in C Explained

Accessing an array outside its allocated boundary in the C programming language leads to undefined behavior. Because C lacks built-in bounds checking, the runtime will attempt to read or modify arbitrary memory addresses based on pointer arithmetic. This article examines why C handles arrays this way, the immediate technical consequences—ranging from silent data corruption to segmentation faults—and the security vulnerabilities that arise from these errors.

The Mechanism: Pointer Arithmetic Without Guards

In C, array indexing is merely syntactic sugar for pointer arithmetic. The expression array[i] translates directly to *(array + i). When you define an array of a specific size, the compiler allocates a contiguous block of memory to store those elements.

When you pass an index that is less than zero or greater than or equal to the array size:

  1. The compiler calculates the target address: base_address + (index * sizeof(element_type)).
  2. The program directly attempts to read or write the byte sequence at that calculated address.
  3. The C standard imposes no requirement on the runtime environment to verify that this calculated address lies within the original array's boundaries.

Because there is no runtime check, the operation enters the realm of undefined behavior. The C standard does not specify or guarantee any particular outcome.

Potential Consequences

When undefined behavior occurs due to out-of-bounds access, several distinct outcomes are possible depending on the operating system, the compiler, and the memory layout:

1. Segmentation Fault (Crash)

If the calculated address points outside the process's allocated address space, or to a page marked with incompatible permissions (such as attempting to write to read-only code memory), the operating system's memory management unit (MMU) intervenes. The OS sends a signal—typically SIGSEGV on Unix-like systems—terminating the process immediately.

2. Silent Memory and Data Corruption

If the index points to memory belonging to the same process, the read or write will succeed without any immediate error:

  • Stack Variables: If the array is a local variable, indexing past its boundary can overwrite adjacent variables, loop counters, or function parameters located on the call stack.
  • Heap Data: If the array is dynamically allocated via malloc, out-of-bounds writes corrupt adjacent heap chunks, internal allocator metadata, or other dynamically allocated objects.

This corruption often causes failures far removed from the actual bug, making tracking and debugging difficult.

3. Security Vulnerabilities (Buffer Overflows)

Out-of-bounds memory access is the foundational flaw behind buffer overflow exploits. An attacker can supply input designed to overflow a local buffer and overwrite the function's return address stored on the stack. When the function returns, execution diverts to code supplied or designated by the attacker, potentially leading to arbitrary code execution.

4. Reading Garbage Data

If an out-of-bounds read accesses memory that happens to be mapped to the process, it returns whatever raw bytes reside at that location. This can leak sensitive internal data (such as cryptographic keys or passwords) and cause logical errors in calculations.

5. Optimization Anomalies

Modern optimizing compilers assume that undefined behavior never occurs in valid C code. If a compiler analyzes an out-of-bounds access, it may optimize away surrounding loops, discard null checks, or eliminate code branches under the assumption that the code path is unreachable.

Prevention Techniques

To avoid out-of-bounds array access:

  • Track Array Bounds: Always pass the size of an array alongside the pointer when passing arrays to functions.
  • Use Static Analysis: Use linters and modern compiler warnings (-Wall, -Wextra, -Warray-bounds) to identify fixed-size boundary violations at compile time.
  • Employ Dynamic Analysis Tools: Compile code with AddressSanitizer (-fsanitize=address) or run the binary through tools like Valgrind to intercept out-of-bounds reads and writes during testing.