我一直在试图弄清楚如何在不调用未定义行为的情况下从 C++17 访问映射缓冲区。对于此示例,我将使用 Vulkan 返回的缓冲区vkMapMemory
。
因此,根据N4659(最终的 C++17 工作草案),第[intro.object]部分(强调添加):
C++ 程序中的构造创建、销毁、引用、访问和操作对象。 当隐式更改联合的活动成员(12.3) 或创建临时对象时(7.4, 15.2) ,通过定义(6.1)、 新表达式(8.3.4) 创建对象。
显然,这些是创建 C++ 对象的唯一有效方法。因此,假设我们得到一个void*
指向主机可见(和一致)设备内存的映射区域的指针(当然,假设所有必需的参数都有有效值并且调用成功,并且返回的内存块足够大并正确对齐):
void* ptr{};
vkMapMemory(device, memory, offset, size, flags, &ptr);
assert(ptr != nullptr);
现在,我希望以float
数组的形式访问此内存。显而易见的事情是static_cast
指向指针并继续我的快乐方式,如下所示:
volatile float* float_array = static_cast<volatile float*>(ptr);
(volatile
包括在内,因为它被映射为连贯内存,因此可以在任何时候由 GPU 写入)。但是,从技术上讲float
,该内存位置中不存在数组,至少在引用摘录的意义上不存在,因此通过这样的指针访问内存将是未定义的行为。因此,根据我的理解,我有两个选择:
1.memcpy
数据
应该始终可以使用本地缓冲区,将其std::byte*
强制memcpy
转换为映射区域。GPU 将按照着色器中的指示解释它(在这种情况下,作为 32 位数组float
),从而解决问题。但是,这需要额外的内存和额外的副本,所以我宁愿避免这种情况。
2.放置-new
数组
似乎[new.delete.placement]部分没有对如何获得放置地址施加任何限制(无论实现的指针安全性如何,它都不必是安全派生的指针)。因此,应该可以通过放置创建一个有效的浮点数组new
,如下所示:
volatile float* float_array = new (ptr) volatile float[sizeInFloats];
现在应该可以安全地访问指针float_array
(在数组的范围内,或过去一次)。
所以,我的问题如下:
- 简单的
static_cast
确实是未定义的行为吗? - 这种展示位置
new
的使用是否定义明确? - 这种技术是否适用于类似情况,例如访问内存映射硬件?
作为旁注,我从来没有通过简单地转换返回的指针而遇到问题,我只是想根据标准的字母找出正确的方法是什么。