[expr.new]C++的5.3.4 2月草案给出了一个例子:
new(2,f) T[5]导致打电话operator new[](sizeof(T)*5+y,2,f).这里,x和y是非负的未指定值,表示数组分配开销; new-expression的结果将从返回的值中抵消此数量
operator new[].这种开销可以应用于所有数组新表达式,包括那些引用库函数operator new[](std::size_t, void*)和其他放置分配函数的表达式.开销的数量可能因新的一次调用而异.- 末端的例子 ]
现在来看以下示例代码:
void* buffer = malloc(sizeof(std::string) * 10);
std::string* p = ::new (buffer) std::string[10];
Run Code Online (Sandbox Code Playgroud)
根据上面的引用,第二行将new (buffer) std::string[10]在内部调用operator new[](sizeof(std::string) * 10 + y, buffer)(在构造单个std::string对象之前).问题是如果y > 0,预分配的缓冲区太小了!
那么我如何知道在使用数组放置时预先分配多少内存?
void* buffer = malloc(sizeof(std::string) * 10 + how_much_additional_space);
std::string* p = ::new (buffer) std::string[10];
Run Code Online (Sandbox Code Playgroud)
或者标准某处是否保证y == 0在这种情况下?报价再次说:
这种开销可以应用于所有数组新表达式,包括那些引用库函数
operator …
在将它用于数组时,是否可以在便携式代码中实际使用新的放置?
看来你从new []返回的指针并不总是和你传入的地址相同(5.3.4,标准中的注释12似乎证实这是正确的),但是我不知道你是怎么回事如果是这种情况,可以为数组分配一个缓冲区.
以下示例显示了该问题.使用Visual Studio编译,此示例导致内存损坏:
#include <new>
#include <stdio.h>
class A
{
public:
A() : data(0) {}
virtual ~A() {}
int data;
};
int main()
{
const int NUMELEMENTS=20;
char *pBuffer = new char[NUMELEMENTS*sizeof(A)];
A *pA = new(pBuffer) A[NUMELEMENTS];
// With VC++, pA will be four bytes higher than pBuffer
printf("Buffer address: %x, Array address: %x\n", pBuffer, pA);
// Debug runtime will assert here due to heap corruption
delete[] pBuffer;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
查看内存,编译器似乎使用缓冲区的前四个字节来存储其中项目数的计数.这意味着因为缓冲区sizeof(A)*NUMELEMENTS很大,所以数组中的最后一个元素被写入未分配的堆中.
所以问题是你能找到你的实现需要多少额外的开销来安全地使用placement new []吗?理想情况下,我需要一种可在不同编译器之间移植的技术.请注意,至少在VC的情况下,不同类的开销似乎不同.例如,如果我删除示例中的虚拟析构函数,则new []返回的地址与我传入的地址相同.
OpenGL 和 Vulkan 都允许分别使用glMapBuffer和获取指向部分 GPU 内存的指针vkMapMemory。他们都给void*映射的内存一个。要将其内容解释为某些数据,必须将其强制转换为适当的类型。最简单的示例可能是转换为 afloat*以将内存解释为浮点数或向量或类似数组。
似乎任何类型的内存映射在 C++ 中都是未定义的行为,因为它没有内存映射的概念。但是,这并不是真正的问题,因为该主题超出了 C++ 标准的范围。但是,仍然存在一个问题volatile。
在链接的问题中,指针被额外标记为volatile因为它指向的内存内容可以以编译器在编译期间无法预料的方式进行修改。这似乎是合理的,尽管我很少看到人们volatile在这种情况下使用(更广泛地说,这个关键字现在似乎很少使用)。
同时在这个问题中,答案似乎是使用volatile是不必要的。这是因为他们所说的内存是映射使用的mmap,然后msync可以被视为修改内存,这类似于在 Vulkan 或 OpenGL 中显式刷新它。恐怕这不适用于 OpenGL 和 Vulkan。
如果内存被映射为未映射GL_MAP_FLUSH_EXPLICIT_BIT或根本VK_MEMORY_PROPERTY_HOST_COHERENT_BIT不需要刷新,则内存内容会自动更新。即使通过使用手动刷新内存,vkFlushMappedMemoryRanges或者glFlushMappedBufferRange这些函数实际上都没有将映射指针作为参数,因此编译器也不可能知道它们修改了映射内存的内容。
因此,是否有必要将指向映射 GPU 内存的指针标记为volatile?我知道从技术上讲这都是未定义的行为,但我问的是在实际硬件中实际需要什么。
顺便说一下,无论是Vulkan 规范还是OpenGL 规范都没有提到volatile限定符。
编辑:将内存标记为volatile会导致性能开销吗?
什么情况下,reinterpret_cast荷兰国际集团一char*(或char[N])是不确定的行为,当它定义的行为?我应该用什么经验来回答这个问题?
正如我们从这个问题中学到的,以下是未定义的行为:
alignas(int) char data[sizeof(int)];
int *myInt = new (data) int; // OK
*myInt = 34; // OK
int i = *reinterpret_cast<int*>(data); // <== UB! have to use std::launder
Run Code Online (Sandbox Code Playgroud)
但是在什么时候我们可以reinterpret_cast在一个char数组上做一个并且它不是未定义的行为?以下是一些简单的例子:
不new,只是reinterpret_cast:
alignas(int) char data[sizeof(int)];
*reinterpret_cast<int*>(data) = 42; // is the first cast write UB?
int i = *reinterpret_cast<int*>(data); // how about a read?
*reinterpret_cast<int*>(data) = 4; // how about the second write?
int j …Run Code Online (Sandbox Code Playgroud)c++ undefined-behavior language-lawyer reinterpret-cast c++17
有多个问题询问mmap和访问共享内存中的结构,同时不通过违反严格的别名规则或违反对象生存期来调用 UB。
一致认为,如果不复制数据,这通常是不可能的。
\n更一般地说,多年来,我看到了无数涉及reinterpret_cast(或更糟)序列化(反序列化)的代码片段,违反了这些规则。我总是建议std::memcpy,同时声称编译器将删除这些副本。我们现在可以做得更好吗?
我想澄清简单地将一堆字节解释为另一种 POD 类型 w\xcc\xb2i\xcc\xb2t\xcc\xb2h\xcc\xb2o\xcc\xb2u\xcc\xb2t\xcc\xb2 的正确方法是什么在 C++20 中复制数据?
\n据我所知,有一个提案P0593R6已被 C++20 接受。
\n根据阅读内容,我相信以下代码是安全的:
\ntemplate <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}\nRun Code Online (Sandbox Code Playgroud)\n#include <cstring>\n#include <array>\n#include <fmt/core.h>\n\nstruct …Run Code Online (Sandbox Code Playgroud) 为避免复制大量数据,最好将mmap二进制文件直接处理原始数据。这种方法有几个优点,包括将分页委托给操作系统。不幸的是,我的理解是明显的实现会导致未定义行为(UB)。
我的用例如下:创建一个二进制文件,其中包含一些标识格式和提供元数据的标头(在这种情况下只是double值的数量)。文件的其余部分包含我希望处理的原始二进制值,而不必先将文件复制到本地缓冲区中(这就是我首先对文件进行内存映射的原因)。下面的程序是一个完整的(如果简单)示例(我相信所有标记为UB[X]导致 UB 的地方):
// C++ Standard Library
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <fstream>
#include <iostream>
#include <numeric>
// POSIX Library (for mmap)
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
constexpr char MAGIC[8] = {"1234567"};
struct Header {
char magic[sizeof(MAGIC)] = {'\0'};
std::uint64_t size = {0};
};
static_assert(sizeof(Header) == 16, "Header size should be 16 bytes");
static_assert(alignof(Header) == 8, "Header alignment should be 8 bytes");
void write_binary_data(const char* filename) {
Header …Run Code Online (Sandbox Code Playgroud)