lefticus / cpp_weekly

The official C++ Weekly Repository. Code samples and notes of future / past episodes will land here at various times. PR's will be accepted in some cases.
The Unlicense
700 stars 26 forks source link

Alignment in C++ (alignof, alignas, std::alignment_of, placement new) #245

Open MattyMuir opened 1 year ago

MattyMuir commented 1 year ago

Channel

C++Weekly

Topics

Discussion about alignment of objects in C++: how to achieve specific alignments on stack & heap and why it's important.

How alignment works with operator new (specifically, how alignment requirements are passed as placement-parameters to placement new).

The standard library function std::alignment_of, and any differences it has from 'alignof'.

It could also be interesting to cover how alignment works with struct packing, or SIMD types (e.g. __m256d and other types in ).

Length

Bite-sized (5-10 minutes)

VictorEijkhout commented 1 year ago

Additionally: some basics on how to write aligned allocators. I want aligned_vector(N,8), aligned_vector(N,4k) and make sure that vector routines can accept any of them without too much overloading.

That would of course make it a rather longer episode.

LB-- commented 1 year ago

@VictorEijkhout Standard library containers already respect the alignment requirements of the type they contain. For example:

struct OverAligned final
{
    alignas(4096) SomeType v;
};
std::vector<OverAligned> vec;
vec.emplace_back();
vec.emplace_back();
assert(std::addressof(vec[1].v) - std::addressof(vec[0].v) == 4096);
VictorEijkhout commented 1 year ago

@LB-- Not what I mean.

vector<char,align<8>> v8(1); 
assert( v8.data()%8==0);
vector<char,align<4096>> v4k(2); 
assert(v4k.data()%4096==0);
assert( &v4k[1]-&v4k[0]==sizeof(char) );

Additionally,

void f( /* you tell me */ ) {};
f(v8); f(v4k); 
LB-- commented 1 year ago

@VictorEijkhout If you are going to be treating the contents as raw memory anyway, then my original reply is still the correct answer, e.g. alignas(4096) std::byte buf[4096]; could be the data member of the struct. You can use std::span<std::byte> or gsl::span<std::byte> to provide the memory range to other functions without including the alignment requirements as part of the type.

Dharmesh946 commented 3 months ago

would be interested to know how to create a storage of bytes, start lifetime of an uninitialized array of T somewhere inside the storage, then create objects there. Use case: non default constructible objects, reusable storage for different types, understanding pmr implementation,...

LB-- commented 3 months ago

@Dharmesh946 For "non default constructible objects, reusable storage for different types" you would just use a union, no need to deal with byte arrays. For allocators and other purposes, typically you use std::construct_at which is also allowed in constexpr just like unions are. Though what isn't allowed in constexpr is type punning from a byte array for that, hence why unions are typically used instead. This is all complicated to figure out though so would be good to see in a video someday.

Dharmesh946 commented 2 months ago

@LB if I understand well, https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p0593r6.html seems to indicate that type punning is ruled out for constant expression. Yet, under the hood, it makes me wonder how pmr are implemented and if they can be used in constant expression (I'm guessing the answer is no)?

LB-- commented 2 months ago

@Dharmesh946 Constant expression allocators generally have to use default new and delete since those are supported by compiler magic. There are some ways to do things with pre-allocated storage for constant expressions but it's not ergonomic, and either way it's difficult to carry allocations from compile time to runtime for e.g. constinit. If PMR works in constexpr at all it's via support for virtual and new in constant expressions. So, in theory it could work if your allocators didn't do anything unusual at compile time.