这是在同一分配中存储不同类型的正确方法吗?
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 char
和std::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。
我需要使用 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 char
和std::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。