Search code examples
cpointersinteger-overflow

How does subtracting pointers p - (p - 1) create integer overflow?


Here it is in code:

#include <stdio.h>
int main()
{
  int i = 3;
  int *p = &i;
  p - (p - 1);
  return 0;
}

The compiler (gcc) warns of integer overflow at the outer subtraction:

[user@comp c]$ gcc foo.c
foo.c: In function ‘main’:
foo.c:6:5: warning: integer overflow in expression [-Woverflow]
   p - (p - 1);
     ^

The correct result, 1, is obtained on my machine.

Why?

Is this because pointer addresses are unsigned ints but ptrdiff_t is a signed int and cannot handle those big numbers?

I saw that

p - (p);

and

p - (p + 1);

do not cause overflow.

I am trying to understand what is going on behind the scenes here. This is my first question on stackoverflow, please let me know if my question can be improved.


Solution

  • Pointer arithmetic is not integer arithmetic. It's defined in terms of address of array elements. If p points to an element of an array, then p-1 points to the previous element of the same array. If that element doesn't exist, the subtraction has undefined behavior.

    For purposes of pointer arithmetic, a single object is treated as a 1-element array. A pointer may point just past the end of an array, but such a pointer may not be dereferenced.

    int i = 3;
    int *p = &i;
    

    So far, so good; p points to i.

    p - (p - 1);
    

    Evaluating p - 1 has undefined behavior. There is no correct result.

    Typically compilers don't generate code to check pointer arithmetic for validity at run time. In a typical implementation, the above will yield the "expected" result of 1. A compiler might even replace the expression with a literal 1 at compile time -- but in doing the analysis required to make that optimization, it may notice that the behavior is undefined and warn you about it.

    As for why you're getting that particular message, that's a question about your compiler, which happens to be gcc. I do not get that message with gcc 4.7.2, but I do get it with both 4.8.0 and 4.9.0. (The command

    gcc --version
    

    tells you which version you're using). gcc is correct to print some warning message, but that particular message is incorrect, since no integer arithmetic is being performed. The "integer overflow" message is a bug in gcc that also causes it to print spurious warnings for valid code. I've submitted a bug report, which is currently expected to be fixed in version 4.8.4.

    p - (p);
    

    That's valid (but the parentheses are unnecessary). Subtraction of two pointers yields the distance, in elements, between the array elements they point to. If they don't point into the same array, or just past the end of it, the behavior is undefined. p - p, given that p is a valid pointer, is simply 0 (of type ptrdiff_t).

    p - (p + 1);
    

    Also valid. p + 1 points past the end of i, which is permitted. The subtraction yields -1, again of type ptrdiff_t.

    Recommended reading: Sections 4 (Pointers) and 6 (Arrays and Pointers) of the comp.lang.c FAQ.