2

Say we declared a char* buffer:

char *buf = new char[sizeof(int)*4]
//to delete:
delete [] buf;

or a void* buffer:

void *buf = operator new(sizeof(int)*4);
//to delete:
operator delete(buf);

How would they differ if they where used exclusively with the purpose of serving as pre-allocated memory?- always casting them to other types(not dereferencing them on their own):

int *intarr = static_cast<int*>(buf);
intarr[1] = 1;

Please also answer if the code above is incorrect and the following should be prefered(only considering the cases where the final types are primitives like int):

int *intarr = static_cast<int*>(buf);
for(size_t i = 0; i<4; i++){
    new(&intarr[i]) int;
}
intarr[1] = 1;

Finally, answer if it is safe to delete the original buffer of type void*/char* once it is used to create other types in it with the latter aproach of placement new.

It is worth clarifying that this question is a matter of curiosity. I firmly believe that by knowing the bases of what is and isnt possible in a programming language, I can use these as building blocks and come up with solutions suitable for every specific use case when I need to in the future. This is not an XY question, as I dont have a specific implementation of code in mind.

In any case, I can name a few things I can relate to this question off the top of my head(pre-allocated buffers specifically):

Sometimes you want to make memory buffers for custom allocation. Sometimes even you want to align these buffers to cache line boundaries or other memory boundaries. Almost always in the name of more performance and sometimes by requirement(e.g. SIMD, if im not mistaken). Note that for alignment you could use std::aligned_alloc()

Matias Chara
  • 921
  • 6
  • 21
  • 1
    Before `void *` existed, programmers used `char *`. `intarr[1] = 1;` is still potentially UB, because the memory may be not aligned to `alignof(int)`. – KamilCuk Jul 19 '20 at 17:10
  • @KamilCuk I believe new always returns memory aligned to the biggest possible primitive type, even if it is used to allocate a byte-wide char. PLEASE, correct me if im wrong. – Matias Chara Jul 19 '20 at 17:14
  • 1
    When you allocate raw memory with `operator new`, `static_cast` it into `int*` and then use the resulting pointer to access array elements, you get UB. It is likely to work as expected, but it is still technically UB. – Evg Jul 19 '20 at 17:15
  • @Evg so I should use the placement new to create int objects in every location I want to access them? – Matias Chara Jul 19 '20 at 17:16
  • 1
    @MatiasChara If you want to use it as an array, you need `[]` version of placement new. But be careful, because it might require more memory than `n * sizeof(T)`. – Evg Jul 19 '20 at 17:21
  • @Evg yes I am aware of this, that is why I did not use it. Is it, however, suficient to iterate over all elements and do placement new on each one of them individualy? – Matias Chara Jul 19 '20 at 17:23
  • 1
    @MatiasChara, if you want to use pointer arithmetic on this sequence (like `arr[i]`), no. Again, it is likely to work, but is UB by the standard so it could fail in an unpredictable way. – Evg Jul 19 '20 at 17:25
  • @Evg but isnt assigning a void* to an int* well defined behaviour even for arrays in c?(malloc does it all the time) – Matias Chara Jul 19 '20 at 17:25
  • 1
    Assigning pointers is fine, dereference is not. C++ is not C, rules are different. – Evg Jul 19 '20 at 17:27
  • 1
    [treating memory returned by `operator new(sizeof(T) * N)` as an array](https://stackoverflow.com/questions/53451770/treating-memory-returned-by-operator-newsizeoft-n-as-an-array) – Evg Jul 19 '20 at 17:28
  • @Evg if it is undefined behaviour to do pointer arithmetic on a casted pre-allocated buffer. How would you go about using pre-allocated buffers with c-style arrays in c++? – Matias Chara Jul 19 '20 at 17:29
  • I guess the only standard-compliant way is to use `[]` placement new. – Evg Jul 19 '20 at 17:31
  • 2
    @Evg If you have a crystal ball and can predict how much memory overhead the implementation will use. Array placement new is not very usable in practice unfortunately. – eerorika Jul 19 '20 at 17:36
  • 1
    @eerorika, agree. – Evg Jul 19 '20 at 17:42
  • 1
    @Evg after doing some research, you are right, it is *techincally* undefined behaviour, but it is really **really** unlinkely that a compiler implementation actually invokes UB. Furthermore, it is common consensus that the fact that it is UB is a shortcoming of the c++ standard and *should* be fixed. – Matias Chara Jul 19 '20 at 19:53

1 Answers1

1

Due to a technicality, delete [] buf; may be considered to be UB after buf has been invalidated due to the array of characters being destroyed due to the reuse of the memory.

I wouldn't expect it to cause problems in practice, but using raw memory from operator new doesn't suffer from this technicality and there is no reason to not prefer it.

Even better is to use this:

T* memory = std::allocator<T>::allocate(n);

Because this will also work for overaligned types (unlike your suggestions) and it is easy to replace with a custom allocator. Or, simply use std::vector


for(size_t i = 0; i<4; i++){
    new(&intarr[i]) int;
}

There is a standard function for this:

std::uninitialized_fill_n(intarr, 4, 0);

OK, it's slightly different that it initialises with an actual value, but that's probably better thing to do anyway.

eerorika
  • 232,697
  • 12
  • 197
  • 326