Negative Array Index in C – When p[-1] Is Perfectly Legal

A negative array index looks illegal — surely p[-1] must be undefined behavior? Not necessarily. This question from our C Programming Quiz App catches people who memorized “negative index = bad” instead of the actual rule: what matters is where the resulting address lands, not the sign of the offset.

The Quiz Question

int arr[] = {10, 20, 30};
int *p = arr + 2;
printf("%d", p[-1]);

What is printed by this code?

  1. 10
  2. 20
  3. 30
  4. Undefined behavior

The Correct Answer: 20

p = arr + 2 parks the pointer on the last element, arr[2]. Since p[-1] is defined as *(p + (-1)), it steps one element back — to arr[1], which is 20. The address stays comfortably inside the array, so the access is fully defined. Verified on gcc 13.3 and Apple clang 21, warning-free under -Wall -Wextra:

$ gcc -Wall -Wextra negidx.c && ./a.out
20
  arr: [ 10 ][ 20 ][ 30 ]
        p-2   p-1    p ← p points here (arr + 2)
                     p[-1] = *(p - 1) = arr[1] = 20

Why Each Wrong Answer Is Wrong

Why not 10?

10 is at p[-2] (also perfectly legal from here — it lands on arr[0]). Choosing 10 usually means thinking “negative index wraps to the start”. There’s no wrapping; it’s plain arithmetic backwards from wherever the pointer currently stands.

Why not 30?

30 is *p — the element under the pointer with no offset. This answer ignores the -1 entirely.

Why not “Undefined behavior”?

This is the popular wrong answer, and it’s almost right thinking applied to the wrong case. The rule (C11 §6.5.6): pointer arithmetic is defined as long as the result stays within the array, or one past its end. p - 1 from arr + 2 lands on arr[1] — inside. But move the starting point and the same syntax genuinely is UB:

int *q = arr;              /* q at arr[0]                  */
printf("%d", q[-1]);       /* one BEFORE the array: UB     */

We ran this on both compilers, and the results are a perfect illustration of undefined behavior’s randomness: clang printed 0, gcc printed 1785567984 — whatever garbage sat below the array on the stack. AddressSanitizer catches it on both:

$ gcc -fsanitize=address negbad.c && ./a.out
==356933==ERROR: AddressSanitizer: stack-buffer-underflow
READ of size 4 at 0x720a3dd0001c thread T0

Same expression, different starting pointer — one is well-defined, the other is a stack-buffer-underflow. The sign of the index was never the issue.

Where Negative Indices Are Actually Used

Real code meets p[-1] whenever a pointer walks forward through a buffer and needs to peek at what it just passed: parsers checking the previous character, sliding-window algorithms, or comparing each element to its predecessor (p[0] < p[-1]) inside a sort. As long as the pointer has advanced at least that far into the array, the backward reference is defined — the same rule that makes forward pointer arithmetic safe in-bounds and undefined outside.

Frequently Asked Questions

Is a negative array index always undefined behavior in C?

No. p[-1] is defined whenever the resulting address is still inside the array — for example when p points at element 2, p[-1] is element 1. It’s undefined only when the result falls outside the array, such as arr[-1] from the base.

What does p[-1] actually compute?

By definition p[-1] is *(p + (-1)): step back one element (one sizeof unit, not one byte) from where p points, then read.

How do I catch out-of-bounds negative indexing?

Compile with -fsanitize=address and run: AddressSanitizer reports a stack-buffer-underflow with the exact line. Compilers alone typically can’t warn, because whether p[-1] is in bounds depends on runtime pointer values.

Related Reading

Recommended Books

This question is #42 in the C Programming Quiz App — 155 questions with explanations covering operators, pointers, memory, and more.
Download on Google Play →

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>