Soc*_*ras 23 c++ volatile language-lawyer mapped-memory c++17
我一直试图弄清楚如何从C++ 17访问映射缓冲区而不调用未定义的行为.对于这个例子,我将使用Vulkan的返回缓冲区vkMapMemory.
因此,根据N4659(最终的C++ 17工作草案),[intro.object]部分(重点补充):
C++程序中的构造创建,销毁,引用,访问和操作对象.一个目的是通过一种创建定义(6.1),通过一个 新的表达式 (8.3.4)中,当隐式地改变所述一个联合的活性部件(12.3),或当一个临时对象被创建(7.4,15.2).
显然,这些是创建C++对象的唯一有效方法.因此,假设我们得到一个void*指向主机可见(和相干)设备内存的映射区域的指针(当然,假设所有必需的参数都有有效值并且调用成功,并且返回的内存块足够大)并正确对齐):
void* ptr{};
vkMapMemory(device, memory, offset, size, flags, &ptr);
assert(ptr != nullptr);
Run Code Online (Sandbox Code Playgroud)
现在,我希望将此内存作为float数组访问.显而易见的事情static_cast是指针并按照我的快乐方式继续如下:
volatile float* float_array = static_cast<volatile float*>(ptr);
Run Code Online (Sandbox Code Playgroud)
(volatile包含它因为它被映射为相干存储器,因此可以在任何时候由GPU写入).然而,在该存储器位置中技术上float不存在阵列,至少不是在引用的摘录的意义上,因此通过这样的指针访问存储器将是未定义的行为.因此,根据我的理解,我有两种选择:
memcpy数据它应该总是能够使用本地缓存,将它转换为std::byte*和memcpy的表示到映射区域.GPU将按照着色器中的指示解释它(在这种情况下,作为32位数组float),从而解决了问题.但是,这需要额外的内存和额外的副本,所以我宁愿避免这种情况.
new阵列看来,[new.delete.placement]部分没有对如何获得放置地址施加任何限制(无论实现的指针安全性如何,它都不必是安全派生的指针).因此,可以通过放置创建有效的浮点数组new,如下所示:
volatile float* float_array = new (ptr) volatile float[sizeInFloats];
Run Code Online (Sandbox Code Playgroud)
现在指针float_array应该可以安全访问(在数组的范围内,或者一个过去).
所以,我的问题如下:
static_cast确实是未定义的行为吗? new用法是否定义明确? 作为旁注,根据标准的字母,我只是通过简单地转换返回的指针来解决问题,我只是想弄清楚这样做的正确方法是什么.
根据标准,涉及硬件映射内存的所有内容都是未定义的行为,因为抽象机器不存在该概念.您应该参考您的实施手册.
即使硬件映射的内存是标准的未定义行为,我们可以想象任何理智的实现提供一些服从的通用规则.有些构造比其他构造更加未定义(无论这意味着什么).
简单的
static_cast确实是未定义的行为吗?Run Code Online (Sandbox Code Playgroud)volatile float* float_array = static_cast<volatile float*>(ptr);
是的,这是未定义的行为,并且已在StackOverflow上多次讨论过.
这个展示位置 - 新用法是否定义明确?
Run Code Online (Sandbox Code Playgroud)volatile float* float_array = new (ptr) volatile float[N];
不,即使这看起来很明确,这取决于实现.碰巧的,operator ::new[]允许保留一些开销1,2,你可以不知道有多少,除非您检查您的工具链的文档.因此,::new (dst) T[N]需要大于或等于的未知数量的内存,N*sizeof T并且dst您分配的任何内容可能太小,涉及缓冲区溢出.
怎么办呢?
解决方案是手动构建一系列浮点数:
auto p = static_cast<volatile float*>(ptr);
for (std::size_t n = 0 ; n < N; ++n) {
::new (p+n) volatile float;
}
Run Code Online (Sandbox Code Playgroud)
或者等效地,依赖标准库:
#include <memory>
auto p = static_cast<volatile float*>(ptr);
std::uninitialized_default_construct(p, p+N);
Run Code Online (Sandbox Code Playgroud)
这会在指向的内存中构造连续的N未初始化volatile float对象ptr.这意味着你必须在阅读之前初始化它们; 读取未初始化的对象是未定义的行为.
此技术是否适用于类似情况,例如访问内存映射硬件?
不,这实际上是实现定义的.我们只能假设您的实现采取了合理的选择,但您应该检查其文档所说的内容.
| 归档时间: |
|
| 查看次数: |
906 次 |
| 最近记录: |