相关疑难解决方法(0)

如何在C++中正确访问映射内存而没有未定义的行为

我一直试图弄清楚如何从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不存在阵列,至少不是在引用的摘录的意义上,因此通过这样的指针访问存储器将是未定义的行为.因此,根据我的理解,我有两种选择:

1. memcpy数据

它应该总是能够使用本地缓存,将它转换为std::byte*memcpy表示到映射区域.GPU将按照着色器中的指示解释它(在这种情况下,作为32位数组float),从而解决了问题.但是,这需要额外的内存和额外的副本,所以我宁愿避免这种情况.

2.放置 - new阵列

看来,[new.delete.placement]部分没有对如何获得放置地址施加任何限制(无论实现的指针安全性如何,它都不必是安全派生的指针).因此,可以通过放置创建有效的浮点数组new,如下所示:

volatile …
Run Code Online (Sandbox Code Playgroud)

c++ volatile language-lawyer mapped-memory c++17

23
推荐指数
1
解决办法
906
查看次数

在 C++20 中安全地输入类似 POD 的双关结构?

有多个问题询问mmap和访问共享内存中的结构,同时不通过违反严格的别名规则或违反对象生存期来调用 UB。

\n\n

一致认为,如果不复制数据,这通常是不可能的。

\n

更一般地说,多年来,我看到了无数涉及reinterpret_cast(或更糟)序列化(反序列化)的代码片段,违反了这些规则。我总是建议std::memcpy,同时声称编译器将删除这些副本。我们现在可以做得更好吗?

\n

我想澄清简单地将一堆字节解释为另一种 POD 类型 w\xcc\xb2i\xcc\xb2t\xcc\xb2h\xcc\xb2o\xcc\xb2u\xcc\xb2t\xcc\xb2 的正确方法是什么在 C++20 中复制数据?

\n

据我所知,有一个提案P0593R6已被 C++20 接受。

\n

根据阅读内容,我相信以下代码是安全的:

\n
template <class T>\nT* pune(void* ptr) {\n    // Guaranteed O(1) initialization without overwritting the data.\n    auto* dest = new (ptr) std::byte[sizeof(T)];\n    // There is an implicitly created T, so cast is valid.\n    return reinterpret_cast<T*>(dest);\n}\n
Run Code Online (Sandbox Code Playgroud)\n
#include <cstring>\n#include <array>\n#include <fmt/core.h>\n\nstruct …
Run Code Online (Sandbox Code Playgroud)

c++ c++20

8
推荐指数
1
解决办法
348
查看次数

mmap和C ++严格的别名规则

考虑一个符合POSIX.1-2008的操作系统,并将fd设为有效的文件描述符(对于打开的文件,读取模式,足够的数据...)。以下代码符合C ++ 11标准*(忽略错误检查):

void* map = mmap(NULL, sizeof(int)*10, PROT_READ, MAP_PRIVATE, fd, 0);
int* foo = static_cast<int*>(map);
Run Code Online (Sandbox Code Playgroud)

现在,以下指令是否违反了严格的别名规则?

int bar = *foo;
Run Code Online (Sandbox Code Playgroud)

根据标准:

如果程序尝试通过以下类型之一以外的glvalue访问对象的存储值,则行为未定义:

  • 对象的动态类型,
  • 对象动态类型的cv限定版本,
  • 与对象的动态类型类似(定义见4.4)的类型,
  • 类型是与对象的动态类型相对应的有符号或无符号类型,
  • 一种类型,是与对象的动态类型的CV限定版本相对应的有符号或无符号类型,
  • 集合或联合类型,在其元素或非静态数据成员(递归包括子集合或包含的联合的元素或非静态数据成员)中包括上述类型之一,
  • 该类型是对象动态类型的(可能是cv限定的)基类类型,
  • 字符或无符号字符类型。

map / foo指向的对象的动态类型是什么?那甚至是物体吗?该标准说:

类型T对象的生命周期在以下情况下开始:获得具有类型T正确的对齐方式和大小的存储,并且如果对象具有非平凡的初始化,则其初始化完成。

这是否意味着映射的内存包含10个int对象(假设初始地址已对齐)?但是,如果这是真的,那么这是否也不适用于此代码(这显然会破坏严格的别名)?

char baz[sizeof(int)];
int* p=reinterpret_cast<int*>(&baz);
*p=5;
Run Code Online (Sandbox Code Playgroud)

甚至奇怪的是,这是否意味着声明baz开始了大小为4的任何对象(正确对齐)的生存期?


一些情况:我正在映射一个文件,其中包含我希望直接访问的大量数据。由于此块很大,因此我想避免存储到临时对象。


*在这里可以将nullptr代替NULL,是否将其隐式转换为NULL?该标准有什么参考吗?

c++ posix c++11

2
推荐指数
1
解决办法
430
查看次数

标签 统计

c++ ×3

c++11 ×1

c++17 ×1

c++20 ×1

language-lawyer ×1

mapped-memory ×1

posix ×1

volatile ×1