Strange behavior on shrinking a Dynamic Array -- Capacity reduces to 0
Steven Schveighoffer
schveiguy at yahoo.com
Wed Jun 22 21:03:06 PDT 2011
On Wed, 22 Jun 2011 23:38:30 -0400, d coder <dlang.coder at gmail.com> wrote:
> Hello List
>
> As per TDPL and D documentation, shrinking a dynamic array should not
> relocate it. But that is exactly what is happening.
> On shrinking an array, its capacity is getting reduced to 0. Surprisingly
> the capacity after a shrink is even less than the length
> of the array. As a result the array gets relocated as soon as another
> element is added to it.
A slice capacity of 0 does *not* indicate that 0 bytes are stored in the
slice, it indicates that appending to the slice will reallocate into a new
block. It's a special token meaning "any append to this slice will
reallocate".
There have been a few people that suggested capacity should return *at
least* the number of bytes in the slice. However, the zero indicator can
be useful. It's easier to check for, generates more efficient code when
checking, plus the distinction between "not appendable" and "appendable,
but the current block is full" can be very important. We would lose this
distinction if the default return was the length of the slice.
>
> I have to call reserve again after shrinking it to make sure that the
> array
> is not relocated.
In fact, it is relocated. Reserve will not overwrite data that is already
valid, so it will reallocate the slice into a new array that can hold the
requested size.
There is a function that allows you to reestablish appendability without
reallocating: assumeSafeAppend(). Call this on a slice, and it will
become appendable again (non-zero capacity) at its current location.
However, be cautious as this will *overwrite* any data that was valid
after the slice. This function has no effect on slices of non-heap data.
> import std.stdio;
>
> void main() {
> uint [] arr;
> reserve(arr, 2048);
The above line will allocate an array, and set arr to the first 2048
elements.
> writeln("After reservation:");
> writefln("arr is at: %10s, length: %6s, capacity %6s",
> arr.ptr, arr.length, capacity(arr));
> // Grow the array
> for (size_t i = 0; i < 64; ++i) arr ~= i;
>
> writeln("After appending:");
> writefln("arr is at: %10s, length: %6s, capacity %6s",
> arr.ptr, arr.length, capacity(arr));
I expect arr.ptr to be unchanged, arr.length to be 64, and capacity to be
unchanged.
> arr.length = 32;
Note that even though you have effectively cut off 32 elements from your
slice that latter data is *still valid*. Another slice could be
referencing that data, which is why arr is no longer appendable.
> // reserve(arr, 2048); // does not relocate if uncommented
>
> writeln("After setting length:");
> writefln("arr is at: %10s, length: %6s, capacity %6s",
> arr.ptr, arr.length, capacity(arr));
If you leave reserve commented out, I'd expect arr.ptr to remain
unchanged, length to be 32, and capacity to be 0 (not appendable)
If you uncomment reserve, then arr.ptr will have moved, length will be 32,
and capacity will be >= 2048.
> // grow the array again
> for (size_t i = 0; i < 32; ++i) arr ~= i;
> writeln("After appending again:");
> writefln("arr is at: %10s, length: %6s, capacity %6s",
> arr.ptr, arr.length, capacity(arr));
Regardless of reserve, arr.ptr will have changed from it's original
allocation, arr.length == 64 and capacity will vary depending on whether
reserve was called or not.
-Steve
More information about the Digitalmars-d
mailing list