Piotr Dabkowski
Piotr Dabkowski

Reputation: 5939

C++: Alignment when casting byte buffer to another type

I have a block of memory allocated through char buffer, is it legal to view it via buffer of another type?

char* buffer = new char[1000];
int64_t* int64_view = static_cast<int64_t*>(static_cast<void*>(buffer))

Is int64_view[0] guaranteed to correspond to first 8 bytes of buffer? I am a bit concerned about aliasing, if the char buffer is only 1-byte aligned, and int64_t must be 8-byte aligned then how does compiler handle it?

Upvotes: 2

Views: 810

Answers (2)

Dmitrii Zabotlin
Dmitrii Zabotlin

Reputation: 621

Your example is violation of the strict aliasing rule. So, int64_view anyway will point to the first byte, but it can be unaligned access. Some platforms allow it, some not. Anyway, in C++ it's UB.

For example:

#include <cstdint>
#include <cstddef>
#include <iostream>
#include <iomanip>

#define COUNT 8

struct alignas(1) S
{
    char _pad;
    char buf[COUNT * sizeof(int64_t)];
};

int main()
{
    S s;
    int64_t* int64_view alignas(8) = static_cast<int64_t*>(static_cast<void*>(&s.buf));

    std::cout << std::hex << "s._pad     at " << (void*)(&s._pad) << " aligned as " << alignof(s._pad)     << std::endl;
    std::cout << std::hex << "s.buf      at " << (void*)(s.buf)   << " aligned as " << alignof(s.buf)      << std::endl;
    std::cout << std::hex << "int64_view at " << int64_view       << " aligned as " << alignof(int64_view) << std::endl;

    for(std::size_t i = 0; i < COUNT; ++i)
    {
        int64_view[i] = i;
    }

    for(std::size_t i = 0; i < COUNT; ++i)
    {
        std::cout << std::dec << std::setw(2) << i << std::hex << " " << int64_view + i << " : " << int64_view[i] << std::endl;
    }
}

Now compile and run it with -fsanitize=undefined:

$ g++ -fsanitize=undefined -Wall -Wextra -std=c++20 test.cpp -o test

$ ./test
s._pad     at 0x7ffffeb42300 aligned as 1
s.buf      at 0x7ffffeb42301 aligned as 1
int64_view at 0x7ffffeb42301 aligned as 8
test.cpp:26:23: runtime error: store to misaligned address 0x7ffffeb42301 for type 'int64_t', which requires 8 byte alignment
0x7ffffeb42301: note: pointer points here
 7f 00 00  bf 11 00 00 00 00 00 00  ff ff 00 00 01 00 00 00  20 23 b4 fe ff 7f 00 00  7c a4 9d 2b 98
              ^ 
test.cpp:31:113: runtime error: load of misaligned address 0x7ffffeb42301 for type 'int64_t', which requires 8 byte alignment
0x7ffffeb42301: note: pointer points here
 7f 00 00  bf 00 00 00 00 00 00 00  00 01 00 00 00 00 00 00  00 02 00 00 00 00 00 00  00 03 00 00 00
              ^ 
 0 0x7ffffeb42301 : 0
 1 0x7ffffeb42309 : 1
 2 0x7ffffeb42311 : 2
 3 0x7ffffeb42319 : 3
 4 0x7ffffeb42321 : 4
 5 0x7ffffeb42329 : 5
 6 0x7ffffeb42331 : 6
 7 0x7ffffeb42339 : 7

It works on x86_64, but there is undefined behavior and you pay with execution speed.

This example on godbolt

In C++20 there is bit_cast. It will not help you in this example with unaligned access, but it can resolve some issues with aliasing.

UPDATE: There is instructions on x86_64, that requires aligned access. For example, SSE, that requires 16-bit alignment. If you will try to use these instructions with unaligned access, application will crash with "general protection fault".

Upvotes: 4

Coral Kashri
Coral Kashri

Reputation: 3506

void* will definitely lead to UB. static_cast lost it's value when you cast your type first to the most generic type void*, because you can cast everything to/from void*. It is no different from using reinterpret_cast for casting straight from your type to any other pointer type.

Consider the following example:

int64_t* int64_view = reinterpret_cast<int64_t*>(buffer);

It might work, and it might not - UB.

Upvotes: 0

Related Questions