6

Here is the C code.

int main() {
  int i = 10, *p = &i;
  printf("%ld", p - (int *) NULL);
}

For the pointer arithmetic part, both 'gcc' and 'clang' generate an 'sar rax, 2' instruction in their assembly output. Could someone explain how pointer arithmetic in this case is related to an arithmetic right shift.

phrack101
  • 91
  • 4
  • your code invokes UB. The difference between 2 pointers [must be printed with `%zd`](https://stackoverflow.com/q/586928/995714) – phuclv Apr 09 '19 at 14:47
  • 1
    @phuclv: not exactly: the `printf` format for `ptrdiff_t` is `%td`, `size_t` and `ptrdiff_t` are usually the same size and different signedness, but not guaranteed by the C Standard. Casting the difference as `(long)(p - (int *)NULL)` would produce defined code, but a potentially different result from `printf("%lld", (long long)(p - (int *)NULL));` as for example on 64-bit Windows. – chqrlie Apr 09 '19 at 15:09
  • 1
    Note that the subtraction of two pointers that do not reference the same array (or one past the end of that array) is also undefined behavior. Per [**6.5.6 Additive operators**, paragraph 9 of the C standard](https://port70.net/~nsz/c/c11/n1570.html#6.5.6p9): "When two pointers are subtracted, **both shall point to elements of the same array object, or one past the last element of the array object**. ..." – Andrew Henle Apr 09 '19 at 15:16

1 Answers1

6

Right-shifting by 2 is a fast way to do division by 4. 4 is your int size.

The distance of two pointers to int is the distance of two char pointers corresponding to the int pointers divided by int size (remember, when adding an integer to a pointer,the integer is scaled by pointer-target size, so when you do diffing, you need to undo this scaling).

Technically, you shouldn't be subtracting two unrelated pointers (or printing the difference with "%ld" instead of the proper "%zd") as that's undefined behavior—the standard only allows you to diff pointers pointing to the same object or just past it. Nevertheless, a generic int*-diffing function that doesn't have such undefined behavior by itself:

#include <stddef.h>

ptrdiff_t diff_int_ptr(int *A, int *B)
{
    return A-B;
}

will still translate to the equivalent of

ptrdiff_t diff_int_ptr(int *A, int *B)
{
    return ((char*)A - (char*)B) >> lg2(sizeof(int));
    //strength reduced from: return ((char*)A - (char*)B) / sizeof(int)
    //which is possible iff sizeof(int) is a power of 2
}

on an optimizing compiler (godbold link for x86-64 gcc and clang) as shifting is usually over 10 times faster than division on a modern machine.

PSkocik
  • 52,186
  • 6
  • 79
  • 122