3

What's the correct way to write the code below?

I have a memory manager which provides me with char *'s, but I need to work with arrays of uint32_t. How can I work around the strict aliasing rule? I understand that with a single object it's advised just to copy the contents with memcpy() but that solution is not acceptable for an array of objects.

char* ptr = manager()->Allocate(1000 * sizeof(uint32_));
uint32_t* u32ptr = reinterpret_cast<uint32_t*>(ptr);
....
u32ptr[x] = y;
einpoklum
  • 118,144
  • 57
  • 340
  • 684
Roman
  • 1,351
  • 11
  • 26

2 Answers2

6

You can use placement-new:

uint32_t* u32ptr = new(ptr) uint32_t[1000];

Note that after this, the effective type of the storage is uint32_t, and you may not use ptr any more. You don't have to do anything special with the chars, because for types with a trivial destructor, you can end their lifetime simply by reusing the storage.

alain
  • 11,939
  • 2
  • 31
  • 51
  • Thanks! How can I know how many integers can be placed in the chunk of size X chars ? I am guessing dividing X by sizeof (uint32_t) is wrong.. – Roman Sep 22 '17 at 14:38
  • The size of `char` is guaranteed to be 1 by the standard, so dividing by `sizeof(uint32_t)` is ok. – alain Sep 22 '17 at 14:40
  • Yeah, I meant - alignment issues. say, ptr is is not aligned. I am guessing that placement new will align ints so some bytes won't be useful. I am asking whether there is a "standard" way to know number of items given the pointer and size. – Roman Sep 22 '17 at 14:44
  • Ah ok, now I understood. On x86 alignement is never an issue, for other cases there is a function that returns the maximum alignement, which I don't remember right now. (let me check...) – alain Sep 22 '17 at 14:50
  • Thanks, standard says that the provided pointer must be aligned otherwise placement new has UB. so ptr must be aligned properly. – Roman Sep 22 '17 at 14:53
  • @alain: It _is_ an issue: writes to unaligned `uint32_t`'s are [not atomic](https://stackoverflow.com/questions/36624881/why-is-integer-assignment-on-a-naturally-aligned-variable-atomic-on-x86) AFAICT; and they're potentially slower. – einpoklum Sep 22 '17 at 14:54
  • Perhaps it should also be pointed out that this would actually make a difference only for types whose construction actually involves any work, and for `uint32_t`s it would do basically the same thing – einpoklum Sep 22 '17 at 14:59
  • Yes, for `int32_t` it's not necessary, but by using placement-new the intent is clear and it will not break when the type is changed, so I would write it like this in all cases. – alain Sep 22 '17 at 15:12
  • @einpoklum actually it can make a difference with int32_t as well. it's true that classic unaligned writes are valid on amd64 architectures however modern compilers are using vectorized code and if I use unaligned pointers to int32 array, and pass it further then the generated code might segfault (it actually did in my code) because gcc assumed that the provided pointer is aligned. That happens with "-O3" switch which enables strict aliasing rules. – Roman Sep 22 '17 at 15:19
  • 1
    @einpoklum See here: https://stackoverflow.com/questions/46363384/unaligned-access-store-on-intel-processor – Roman Sep 22 '17 at 15:20
1

You could make the Manager class return std::unique_ptr<void, Manager::Deleter>( that is, with a unique pointer with a custom deleter). This makes the allocation use RAII to automagically deallocate when you go out of scope. And instead of using a pointer, prefer a gsl::span In that case, you could write:

constexpr const length = 1000;
auto allocated = manager()->Allocate(
    length * sizeof(std::uint32_t), 
    alignof(uint32_t) // supporting alignment here is important, otherwise
                      // you'll have to manually take care of that yourself
);
auto p = gsl::span<uint32_t>(new(allocated.get()) std::uint32_t[length], length);

Another alternative is to template the Manager class, or the allocation method, on an element type, and have it take care of things:

auto p = manager()->Allocate<std::uint32_t>(1000);

... and p will be an std::unique_ptr<uint32_t> to costructed uint32_ts. Not that you need any construction for them, but still.

Caveat: In both cases, you must not return p from the scope you're in, since it is a non-owning pointer, and the memory will be freed when you leave the scope. p is for local work only. If you want to keep the memory out-of-scope you have to return the unique_ptr.

einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • But wouldn't the storage be reclaimed as soon as `allocated` goes out of scope, while `p` may still be around? – alain Sep 22 '17 at 14:53
  • @alain: Of course it will - unless the `unique_ptr` is returned from the scope. I'll edit to warn about this. – einpoklum Sep 22 '17 at 14:55