Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pointer subtraction confusion

Tags:

c

People also ask

Is pointer subtraction possible?

The subtraction of two pointers is possible only when they have the same data type. The result is generated by calculating the difference between the addresses of the two pointers and calculating how many bits of data it is according to the pointer data type.

What is the rule for subtracting two pointers?

Two pointers can also be subtracted from each other if the following conditions are satisfied: Both pointers will point to elements of same array; or one past the last element of same array. The result of the subtraction must be representable in ptrdiff_t data type, which is defined in stddef.

Do not subtract or compare two pointers that do not refer to the same array?

Do not subtract or compare two pointers that do not refer to the same array. When two pointers are subtracted, both must point to elements of the same array object or just one past the last element of the array object (C Standard, 6.5.

What is pointer arithmetics?

Address arithmetic is a method of calculating the address of an object with the help of arithmetic operations on pointers and use of pointers in comparison operations. Address arithmetic is also called pointer arithmetic.


The idea is that you're pointing to blocks of memory

+----+----+----+----+----+----+
| 06 | 07 | 08 | 09 | 10 | 11 | mem
+----+----+----+----+----+----+
| 18 | 24 | 17 | 53 | -7 | 14 | data
+----+----+----+----+----+----+

If you have int* p = &(array[5]) then *p will be 14. Going p=p-3 would make *p be 17.

So if you have int* p = &(array[5]) and int *q = &(array[3]), then p-q should be 2, because the pointers are point to memory that are 2 blocks apart.

When dealing with raw memory (arrays, lists, maps, etc) draw lots of boxes! It really helps!


Because everything in pointer-land is about offsets. When you say:

int array[10];
array[7] = 42;

What you're actually saying in the second line is:

*( &array[0] + 7 ) = 42;

Literally translated as:

* = "what's at"
(
  & = "the address of"
  array[0] = "the first slot in array"
  plus 7
)
set that thing to 42

And if we can add 7 to make the offset point to the right place, we need to be able to have the opposite in place, otherwise we don't have symmetry in our math. If:

&array[0] + 7 == &array[7]

Then, for sanity and symmetry:

&array[7] - &array[0] == 7

So that the answer is the same even on platforms where integers are different lengths.


Say you have an array of 10 integers:

int intArray[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

Then you take a pointer to intArray:

int *p = intArray;

Then you increment p:

p++;

What you would expect, because p starts at intArray[0], is for the incremented value of p to be intArray[1]. That's why pointer arithmetic works like that. See the code here.


"When you subtract two pointers, as long as they point into the same array, the result is the number of elements separating them"

Check for more here.


This way pointer subtraction behaves is consistent with the behaviour of pointer addition. It means that p1 + (p2 - p1) == p2 (where p1 and p2 are pointers into the same array).

Pointer addition (adding an integer to a pointer) behaves in a similar way: p1 + 1 gives you the address of the next item in the array, rather than the next byte in the array - which would be a fairly useless and unsafe thing to do.

The language could have been designed so that pointers are added and subtracted the same way as integers, but it would have meant writing pointer arithmetic differently, and having to take into account the size of the type pointed to:

  • p2 = p1 + n * sizeof(*p1) instead of p2 = p1 + n
  • n = (p2 - p1) / sizeof(*p1) instead of n = p2 - p1

So the result would be code that is longer, and harder to read, and easier to make mistakes in.


When applying arithmetic operations on pointers of a specific type, you always want the resulting pointer to point to a "valid" (meaning the right step size) memory-address relative to the original starting-point. That is a very comfortable way of accessing data in memory independently from the underlying architecture.

If you want to use a different "step-size" you can always cast the pointer to the desired type:

int a = 5;
int* pointer_int = &a;
double* pointer_double = (double*)pointer_int; /* totally useless in that case, but it works */