这是在同一分配中存储不同类型的正确方法吗?

Is this a correct way to store different types in the same allocation?

我需要使用 malloc 分配一块内存,然后在其中存储不同普通旧数据类型的多个值。以下是正确的做法吗?

#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <iostream>

struct Foo {
    int32_t a;
    int32_t b;
};

struct Bar {
    int8_t a;
    int8_t b;
};

void* offset_bytewise(void* void_ptr, ptrdiff_t count) {
    return static_cast<void*>(static_cast<char*>(void_ptr) + count);
}

void test() {
    auto void_ptr = std::malloc(10);
    new (offset_bytewise(void_ptr, 0)) Foo{ 1, 2 };
    new (offset_bytewise(void_ptr, 8)) Bar{ 3, 4 };
    auto foo_ptr = static_cast<Foo*>(offset_bytewise(void_ptr, 0));
    auto bar_ptr = static_cast<Bar*>(offset_bytewise(void_ptr, 8));

    std::cout << (int)foo_ptr->a << ' ' << (int)foo_ptr->b << '\n';
    std::cout << (int)bar_ptr->a << ' ' << (int)bar_ptr->b << '\n';

    // Re-using a part of the allocation as a different type than before.
    new (offset_bytewise(void_ptr, 1)) Bar{ 5, 6 };
    bar_ptr = static_cast<Bar*>(offset_bytewise(void_ptr, 1));

    // I suppose dereferencing `foo_ptr` would be UB now?

    std::cout << (int)bar_ptr->a << ' ' << (int)bar_ptr->b << '\n';
    std::free(void_ptr);
}

隐式对象创建将导致分配的内存块包含一个大小合适的 char 数组,并且从 malloc 编辑的指针 return 将指向该数组的第一个元素数组。

(假设 std::malloc 不是 return 空指针,您应该检查它。)

这样的数组可以为其他对象提供存储空间(虽然技术上目前的标准说这只适用于unsigned charstd::byte,这可能是一个缺陷)。

所以您可以在该数组中创建新对象。

但是,当您尝试获取指向新创建的对象的指针时,您不能简单地转换指针,因为数组的元素不是 pointer-interconvertible 数组为其提供存储的对象。

因此您需要在转换指针后应用std::launder,例如

auto foo_ptr = std::launder(static_cast<Foo*>(offset_bytewise(void_ptr, 0)));

或者,您可以使用 new 的结果,它是指向新创建对象的指针:

auto foo_ptr = new(offset_bytewise(void_ptr, 0)) Foo{ 1, 2 };

此外,您需要确保类型的大小和对齐方式正确,即您应该在此处添加:

static_assert(alignof(Foo) <= 8);
static_assert(sizeof(Foo) <= 8);
static_assert(alignof(Bar) <= 1);
static_assert(sizeof(Bar) <= 2);

在偏移 1 处创建第二个 Bar 对象将导致 Foo 对象的生命周期结束,因为它与新对象的存储重叠。因此 foo_ptr 之后只能以其他指向对象 out-of-lifetime 的指针的方式使用。特别是尝试访问成员的值将是 UB。