xiv*_*r77 22 c++ memory-management
到目前为止,我对这个问题的答案有两个完全相反的答案:"它是安全的"和"它是未定义的行为".为了我和任何可能通过Google到达这里的人,我决定整体重写这个问题,以获得更好的澄清答案.
此外,我删除了C标签,现在这个问题是C++特定的
我正在制作一个8字节对齐的内存堆,将在我的虚拟机中使用.我能想到的最明显的方法是分配一个数组std::uint64_t.
std::unique_ptr<std::uint64_t[]> block(new std::uint64_t[100]);
Run Code Online (Sandbox Code Playgroud)
让我们假设sizeof(float) == 4和sizeof(double) == 8.我想存储一个float和一个double block并打印该值.
float* pf = reinterpret_cast<float*>(&block[0]);
double* pd = reinterpret_cast<double*>(&block[1]);
*pf = 1.1;
*pd = 2.2;
std::cout << *pf << std::endl;
std::cout << *pd << std::endl;
Run Code Online (Sandbox Code Playgroud)
我还想存储一个说"你好"的C字符串.
char* pc = reinterpret_cast<char*>(&block[2]);
std::strcpy(pc, "hello\n");
std::cout << pc;
Run Code Online (Sandbox Code Playgroud)
现在我要存储"Hello,world!" 超过8个字节,但我仍然可以使用2个连续的单元格.
char* pc2 = reinterpret_cast<char*>(&block[3]);
std::strcpy(pc2, "Hello, world\n");
std::cout << pc2;
Run Code Online (Sandbox Code Playgroud)
对于整数,我不需要reinterpret_cast.
block[5] = 1;
std::cout << block[5] << std::endl;
Run Code Online (Sandbox Code Playgroud)
我将其分配block为一个数组,仅std::uint64_t用于内存对齐.我也不希望它自己存储大于8个字节的内容.如果起始地址保证为8字节对齐,则块的类型可以是任何类型.
有些人已经回答说我正在做的事情是完全安全的,但有些人说我肯定会调用未定义的行为.
我是否正在编写正确的代码来执行我的意图?如果没有,适当的方式是什么?
Nia*_*all 13
要分配任意(无类型)内存块,全局分配函数(§3.7.4/ 2);
Run Code Online (Sandbox Code Playgroud)void* operator new(std::size_t); void* operator new[](std::size_t);
可以用来做(§3.7.4.1/ 2).
§3.7.4.1/ 2
分配功能尝试分配所请求的存储量.如果成功,它将返回存储块的起始地址,其长度以字节为单位应至少与请求的大小一样大.从分配函数返回时,分配的存储的内容没有限制.未指定由连续调用分配函数分配的存储的顺序,连续性和初始值.返回的指针应适当对齐,以便可以将其转换为具有基本对齐要求(3.11)的任何完整对象类型的指针,然后用于访问分配的存储中的对象或数组(直到存储明确解除分配为止)调用相应的释放函数).
3.11就基本对齐要求说了这个话题 ;
§3.11/ 2
基本对齐由小于或等于所有上下文中的实现所支持的最大对齐的对齐来表示,其等于
alignof(std::max_align_t).
只是为了确保分配函数必须像这样的要求;
§3.7.4/ 3
C++程序中定义的任何分配和/或释放函数,包括库中的缺省版本,都应符合3.7.4.1和3.7.4.2中规定的语义.
假设8字节对齐小于平台的基本对齐(它看起来像是,但这可以在目标平台上验证static_assert(alignof(std::max_align_t) >= 8)) - 您可以使用全局::operator new来分配所需的内存.分配后,可以根据您的尺寸和对齐要求对存储器进行分段和使用.
这里另一种方法是在std::aligned_storage和它能够给你内存无论要求是一致.
typename std::aligned_storage<sizeof(T), alignof(T)>::type buffer[100];
Run Code Online (Sandbox Code Playgroud)
从这个问题来看,我在这里假设大小和对齐T都是8.
最终内存块的样子是(包括基本RAII);
struct DataBlock {
const std::size_t element_count;
static constexpr std::size_t element_size = 8;
void * data = nullptr;
explicit DataBlock(size_t elements) : element_count(elements)
{
data = ::operator new(elements * element_size);
}
~DataBlock()
{
::operator delete(data);
}
DataBlock(DataBlock&) = delete; // no copy
DataBlock& operator=(DataBlock&) = delete; // no assign
// probably shouldn't move either
DataBlock(DataBlock&&) = delete;
DataBlock& operator=(DataBlock&&) = delete;
template <class T>
T* get_location(std::size_t index)
{
// https://stackoverflow.com/a/6449951/3747990
// C++ WD n4527 3.9.2/4
void* t = reinterpret_cast<void*>(reinterpret_cast<unsigned char*>(data) + index*element_size);
// 5.2.9/13
return static_cast<T*>(t);
// C++ WD n4527 5.2.10/7 would allow this to be condensed
//T* t = reinterpret_cast<T*>(reinterpret_cast<unsigned char*>(data) + index*element_size);
//return t;
}
};
// ....
DataBlock block(100);
Run Code Online (Sandbox Code Playgroud)
我已经构建了更详细的示例,DataBlock包括合适的模板construct和get函数等,现场演示以及进一步的错误检查等.
看起来原始代码中存在一些别名问题(严格来说); 你分配一种类型的内存并将其转换为另一种类型.
它可能在您的目标平台上按预期工作,但您不能依赖它.我在这方面看到的最实用的评论是;
您可能会使用的代码.我认为最好使用适当的全局分配函数,并确保在分配和使用所需的内存时没有未定义的行为.
别名仍然适用; 一旦分配了内存 - 别名适用于它的使用方式.一旦分配了任意内存块(如上面的全局分配函数)并且对象的生命周期开始(§3.8/ 1) - 应用别名规则.
std::allocator?虽然std::allocator用于同构数据容器以及您正在寻找的类似于异构分配,但标准库中的实现(给定Allocator概念)提供了有关原始内存分配和所需对象的相应构造的一些指导.
更新新问题:
好消息是有一个简单易用的解决方案来解决你的真正问题:用new(unsigned char[size])分配内存.分配的内存new在标准中保证以适合用作任何类型的方式对齐,并且您可以安全地使用任何类型别名char*.
标准参考,3.7.3.1/2,分配函数:
返回的指针应适当对齐,以便可以将其转换为任何完整对象类型的指针,然后用于访问分配的存储中的对象或数组
原始问题的原始答案:
至少在3.10/15中的C++ 98/03中,我们有以下内容,这显然使它仍然是未定义的行为(因为你通过未在例外列表中枚举的类型访问该值):
如果程序试图通过不同于以下类型之一的左值访问对象的存储值,则行为未定义):
- 对象的动态类型,
- 对象的动态类型的cvqualified版本,
- 与对象的动态类型对应的有符号或无符号类型的类型,
- 对应于对象动态类型的cvqualified版本的有符号或无符号类型,
- 在其成员中包含上述类型之一的聚合或联合类型(包括递归地,子聚合或包含联合的成员),
- 一种类型,是对象的动态类型的(可能是cvqualified)基类类型,
- char或unsigned char类型.