Pixelchemist Pixelchemist - 1 month ago 8
C++ Question

Utilize memory past the end of a std::vector using a custom overallocating allocator

Let's say I have an allocator

my_allocator
that will always allocate memory for
n+x
(instead of
n
) elements when
allocate(n)
is called.

Can I savely assume that memory in the range
[data()+n, data()+n+x)
(for a
std::vector<T, my_allocator<T>>
) is accessible/valid for further use (i.e. placement new or simd loads/stores in case of fundamentals (as long as there is no reallocation)?

Note: I'm aware that everything past
data()+n-1
is uninitialized storage. The use case would be a vector of fundamental types (which do not have a constructor anyway) using the custom allocator to avoid having special corner cases when throwing simd intrinsics at the vector.
my_allocator
shall allocate storage that is 1.) properly aligned and has 2.) a size that is a multiple of the used register size.





To make things a little bit more clear:

Let's say I have two vectors and I want to add them:

std::vector<double, my_allocator<double>> a(n), b(n);
// fill them ...
auto c = a + b;
assert(c.size() == n);


If the storage obtained from
my_allocator
now allocates aligned storage and if
sizeof(double)*(n+x)
is always a multiple of the used simd register size (and thus a multiple of the number of values per register) I assume that I can do something like

for(size_t i=0u; i<(n+x); i+=y)
{ // where y is the number of doubles per register and and divisor of (n+x)
auto ma = _aligned_load(a.data() + i);
auto mb = _aligned_load(b.data() + i);
_aligned_store(c.data() + i, _simd_add(ma, mb));
}


where I don't have to care about any special case like unaligned loads or backlog from some
n
that is not dividable by y.

But still the vectors only contain
n
values and can be handled like vectors of size
n
.

Answer

For your use case you shouldn't have any doubts. However, if you decide to store anything useful in the extra space and will allow the size of your vector to change during its lifetime, you will probably run into problems dealing with the possibility of reallocation - how are you going to transfer the extra data from the old allocation to the new allocation given that reallocation happens as a result of separate calls to allocate() and deallocate() with no direct connection between them?


EDIT (addressing the code added to the question)

In my original answer I meant that you shouldn't have any problem accessing the extra bytes allocated by your allocator in excess of what was requested. However, writing data in the memory range, that is outside the range currently utilized by the vector object but belongs to the range that would be spanned by the unmodified allocation, asks for trouble. An implementation of std::vector is free to request from the allocator more memory than would be exposed through its size()/capacity() functions and store auxiliary data in the unused area. Though this is highly theoretical, not accounting for that possibility means opening a door into undefined behavior.

Consider the following possible layout of the vector's allocation:

---====================++++++++++------.........
  1. === - used capacity of the vector
  2. +++ - unused capacity of the vector
  3. --- - overallocated by the vector (but not shown as part of its capacity)
  4. ... - overallocated by your allocator

You MUST NOT write anything in the regions 2 (---) and 3 (+++). All your writes must be constrained to the region 4 (...), otherwise you may corrupt important bits.

Comments