我一直在尝试找出如何在C++17中访问映射缓冲区而不会引发未定义行为。对于此示例,我将使用由Vulkan的vkMapMemory
返回的缓冲区。
因此,根据N4659(最终的C++17工作草案),第[intro.object]部分(已添加强调):
一个C++程序中的构造创建、销毁、引用、访问和操作对象。通过定义(6.1)、new-expression(8.3.4)、当隐式更改联合的活动成员(12.3)或创建临时对象时(7.4、15.2)创建对象。
这些显然是创建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*
并将表示复制到映射区域。 GPU将按照着色器中的指令(在本例中作为32位 float
数组)解释它,因此问题得以解决。但是,这需要额外的内存和额外的复制,因此我更喜欢避免这种情况。
2. 在放置时使用new
创建数组
似乎[new.delete.placement]部分不对获取放置地址施加任何限制(无论实现的指针安全性如何,它都不需要是 安全派生指针 )。因此,应该可以通过placement-new
创建有效的float数组,如下所示:
volatile float* float_array = new (ptr) volatile float[sizeInFloats];
指针float_array
现在可以安全地访问(在数组边界内或越过一位)。
所以,我的问题如下:
- 简单的
static_cast
是否确实是未定义行为? - 这种放置-
new
用法是否定义明确? - 这种技术适用于类似的情况,例如访问内存映射硬件吗?
顺便说一句,我只是尝试弄清楚根据标准来说这样做的正确方式,我从未遇到过这样的问题,即通过简单的强制类型转换返回指针。
vkMapMemory
的工作方式,因此必须假设float
已经被正确创建,并且在这种情况下UB不会产生任何后果。 - HolyBlackCat