c++ - 使用 memcpy 和 memset 重新分配数组
问题描述
我已经接管了一些代码,并遇到了一个奇怪的数组重新分配。这是 Array 类中的一个函数(由 JsonValue 使用)
void reserve( uint32_t newCapacity ) {
if ( newCapacity > length + additionalCapacity ) {
newCapacity = std::min( newCapacity, length + std::numeric_limits<decltype( additionalCapacity )>::max() );
JsonValue *newPtr = new JsonValue[newCapacity];
if ( length > 0 ) {
memcpy( newPtr, values, length * sizeof( JsonValue ) );
memset( values, 0, length * sizeof( JsonValue ) );
}
delete[] values;
values = newPtr;
additionalCapacity = uint16_t( newCapacity - length );
}
}
我明白了这一点;它只是分配一个新数组,并将旧数组中的内存内容复制到新数组中,然后将旧数组的内容归零。我也知道这样做是为了防止调用析构函数和移动。
这JsonValue
是一个具有函数的类,以及一些存储在联合中的数据(字符串、数组、数字等)。
我担心的是这是否是实际定义的行为。我知道它有效,并且自从我们几个月前开始使用它以来就没有出现过问题;但如果它未定义,则并不意味着它会继续工作。
编辑:
JsonValue
看起来像这样:
struct JsonValue {
// …
~JsonValue() {
switch ( details.type ) {
case Type::Array:
case Type::Object:
array.destroy();
break;
case Type::String:
delete[] string.buffer;
break;
default: break;
}
}
private:
struct Details {
Key key = Key::Unknown;
Type type = Type::Null; // (0)
};
union {
Array array;
String string;
EmbedString embedString;
Number number;
Details details;
};
};
where是sArray
数组的包装器, is a ,is ,是, 和 的并集,包含它所持有的值的类型。所有值的开头都有 16 位未使用的数据,用于. 例子:JsonValue
String
char*
EmbedString
char[14]
Number
int
unsigned int
double
Details
Details
struct EmbedString {
uint16_t : 16;
char buffer[14] = { 0 };
};
解决方案
这段代码是否具有良好定义的行为基本上取决于两件事:1) 是可JsonValue
简单复制的,2) 如果是这样,一堆全零字节是否是 a 的有效对象表示JsonValue
。
如果JsonValue
是可简单复制的,那么memcpy
从一个JsonValue
s 数组到另一个数组确实等同于将所有元素复制到[basic.types]/3上。如果全零是 a 的有效对象表示JsonValue
,那么memset
应该没问题(我相信这实际上属于标准当前措辞的灰色区域,但我相信至少意图是这样很好)。
我不确定你为什么需要“防止调用析构函数和移动”,但是用零覆盖对象并不能阻止析构函数的运行。delete[] values
将调用数组成员的析构函数。并且移动一个可简单复制类型的数组的元素应该编译为只是复制字节。
此外,我建议摆脱这些String
和EmbedString
类并简单地使用std::string
. 至少,在我看来,唯一的目的EmbedString
是手动执行小字符串优化。任何std::string
物有所值的实现都已经在幕后做到了。请注意,std::string
不能保证(并且通常不会)可以轻松复制。因此,您不能在保留当前实现的其余部分的同时简单地替换String
and EmbedString
with 。std::string
如果您可以使用 C++17,我建议您简单地使用std::variant
而不是或至少在此自定义JsonValue
实现中使用,因为这似乎正是它想要做的。如果您需要在任何变量值之前存储一些公共信息,只需让一个合适的成员在持有变量值的成员前面保存该信息,而不是依赖于从同一对开始的工会的每个成员成员(只有在所有联合成员都是标准布局类型且将这些信息保存在它们共同的初始序列[class.mem]/23中时,才会被明确定义)。
的唯一目的Array
似乎是作为一个向量,在出于安全原因释放内存之前将其清零。如果是这种情况,我建议只使用std::vector
带有分配器的分配器,该分配器在解除分配之前将内存归零。例如:
template <typename T>
struct ZeroingAllocator
{
using value_type = T;
T* allocate(std::size_t N)
{
return reinterpret_cast<T*>(new unsigned char[N * sizeof(T)]);
}
void deallocate(T* buffer, std::size_t N) noexcept
{
auto ptr = reinterpret_cast<volatile unsigned char*>(buffer);
std::fill(ptr, ptr + N, 0);
delete[] reinterpret_cast<unsigned char*>(buffer);
}
};
template <typename A, typename B>
bool operator ==(const ZeroingAllocator<A>&, const ZeroingAllocator<B>&) noexcept { return true; }
template <typename A, typename B>
bool operator !=(const ZeroingAllocator<A>&, const ZeroingAllocator<B>&) noexcept { return false; }
进而
using Array = std::vector<JsonValue, ZeroingAllocator<JsonValue>>;
注意:我通过填充内存volatile unsigned char*
来防止编译器优化归零。如果您需要支持过度对齐的类型,您可以用直接调用and 替换 and new[]
(这样做会阻止编译器优化分配)。在 C++17 之前,您必须分配足够大的缓冲区,然后手动对齐指针,例如,使用...</p>
delete[]
::operator new
::operator delete
std::align
推荐阅读
- javascript - 无法获取 Microsoft Graph API javascript ajax 调用的访问令牌
- ios - 当我点击播放器 API 到 Rounaz Cricket API 时,它会发回给我“AccessDenied”
- javascript - 开玩笑测试(使用 babel)无效的令牌导入
- javascript - Javascript对象数组重组
- jquery - Jquery - 按钮无法与类正确触发
- java - 如何从 .class 文件生成 C(本机)头文件(已编译)
- javascript - 当我向下滚动页面时,Particles.js 不会继续 - HTML
- python - pytorch,如何制作相同大小的张量模型(x)和答案(x)?
- python - 如何使用 utf-8 编码制作 lxml 输出文件
- nix - 配置 ~/.config/nixpkgs/config.nix 的位置