Is D's pointer subtraction more permissive than C (and C++)?
Steven Schveighoffer
schveiguy at gmail.com
Fri Apr 1 17:39:24 UTC 2022
On 4/1/22 11:52 AM, Ali Çehreli wrote:
> As the following quote from a Microsoft document claims, and as I've
> already known, pointer subtraction is legal only if the pointers are
> into the same array: "ANSI 3.3.6, 4.1.1 The type of integer required to
> hold the difference between two pointers to elements of the same array,
> ptrdiff_t." (
> https://docs.microsoft.com/en-us/cpp/c-language/pointer-subtraction?view=msvc-170
> )
>
> I suspect "array" means "a block of memory" there because arrays are
> ordinarily malloc'ed pieces of memory in C.
I'm assuming this has to do with the ability to detect artifacts of how
the compiler/library lays out memory, which shouldn't really figure into
program behavior.
In practice, I don't see how it affects the behavior *of the compiler*.
When you subtract two pointers, I don't see how the compiler/optimizer
can make some other decision based on the subtraction not being between
two pointers to the same block of memory.
>
> 1) Is D more permissive (or anemic in documentation:))? I ask because
> paragraph 5 below does not mention the pointers should be related in any
> way:
>
> https://dlang.org/spec/expression.html#pointer_arithmetic
I assume this is because nobody thought about it? But I don't see a
problem with omitting that requirement.
>
> 2) Is subtracting pointers that used to be in the same array legal.
>
> void main() {
> auto a = [ 1, 2 ];
> auto b = a;
> assert(a.ptr - b.ptr == 0); // i) Obviously legal?
>
> // Drop the first element
> a = a[1..$];
> assert(a.ptr - b.ptr == 1); // ii) GC-behaviorally legal?
>
> // Save the pointer
> const old_aPtr = a.ptr;
> // and move the array to another memory
> a.length = 1_000_000;
> // Expect a and b are on different blocks of memory
> assert(a.ptr != old_aPtr);
>
> assert(old_aPtr - b.ptr == 1); // iii) Practically legal?
> }
Assuming C rules, I still think all this is legal. I'd even hazard to
guess it's legal to do this:
```c
struct S
{
int arr1[5], arr2[5];
}
void foo() {
S s;
ptrdiff_t p = &s.arr1[0] - &s.arr2[0];
}
```
Because you know the relationship between the pointers is defined. I.e.
this is NEVER going to change from run to run, or build to build.
>
> Regardless of your answer, I will go ahead and perform that last
> subtraction :).
>
> Ali
>
> P.S. I am trying to implement a type where slices will follow the
> elements as the elements may be moved in memory:
>
> const old_aPtr = a.ptr;
>
> a ~= e;
> if (a.ptr != old_aPtr) {
> // Elements are moved; adjust the slice
> assert(b.ptr >= old_aPtr);
> const old_bOffset = b.ptr - old_aPtr;
> b = a[old_bOffset .. $];
> }
>
> If you ask why I don't keep offsets instead of slices to begin with, I
> want to use pointers (implicitly in D slices) so that they participate
> in the ownership of array elements so that the GC does not free earlier
> elements as the buffer is popFronted as well.
This should be fine. I would suggest to store things as offsets anyway,
and have accessors for the pointers.
-Steve
More information about the Digitalmars-d
mailing list