使用 memcpy 和 memset 重新分配数组
Reallocate array with memcpy and 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;
};
};
其中Array
是围绕JsonValue
数组的包装器,String
是char*
,EmbedString
是char[14]
,Number
是int
、unsigned int
和double
的并集,Details
包含它所持有的值的类型。所有值的开头都有 16 位未使用的数据,用于Details
。例:
struct EmbedString {
uint16_t : 16;
char buffer[14] = { 0 };
};
此代码是否具有明确定义的行为基本上取决于两件事:1( 是否JsonValue
平凡可复制,2( 如果是,一堆全零字节是否是JsonValue
的有效对象表示形式。
如果JsonValue
是微不足道的可复制的,那么从一个JsonValue
数组到另一个数组的memcpy
确实等效于 通过 [basic.types]/3 复制所有元素。如果全零是JsonValue
的有效对象表示,那么memset
应该没问题(我相信这实际上与标准的当前措辞有点灰色地带,但我相信至少意图是这很好(。
我不确定为什么您需要"防止调用析构函数和移动",但用零覆盖对象不会阻止析构函数运行。delete[] values
将调用数组成员的析构函数。并且移动一个简单可复制类型的数组的元素应该编译为仅复制字节。
此外,我建议摆脱这些String
和EmbedString
类,只需使用std::string
。至少,在我看来,EmbedString
的唯一目的是手动执行小字符串优化。任何值得一提的std::string
实现都已经在引擎盖下做到这一点。请注意,std::string
不能保证(并且通常不会(简单可复制。因此,您不能简单地将String
和EmbedString
替换为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*
填充内存,以防止编译器优化归零。如果需要支持过度对齐的类型,可以将new[]
和delete[]
替换为对::operator new
和::operator delete
的直接调用(这样做将阻止编译器优化分配(。在 C++17 之前,您必须分配足够大的缓冲区,然后手动对齐指针,例如,使用std::align
...
- 将数组的地址分配给变量并删除
- 使用动态分配的数组会导致代码分析发出虚假的C6386缓冲区溢出警告
- 获取字符串的长度并将其分配给数组
- 在c++中为我自己的基于指针的数组分配内存的正确方法
- 使用递归模板动态分配的多维数组
- 如何正确填充在堆上分配的二维数组?
- 在函数范围内在堆栈上分配的数组在离开函数时是否总是被释放?
- 在 C++ 中搜索动态分配的数组中的出现次数
- 动态分配的聊天数组打印缺失的数据和空
- 关于字符数组,我正在尝试将数组中的每个字符分配给另一个值
- 我似乎无法为指针分配一个数组,然后更改数组的内容
- 具有自定义构造函数 (C++) 的类型的动态数组分配
- 无法分配C++数组
- 如何增加以前由新运算符分配的 C++ std::list 数组的大小?
- 分配 2D 可变大小的数组
- 介于 [固定数组] 和 [带内存分配的指针] 之间的性能
- 动态分配字符数组的内存
- C++具有动态分配的字符数组的结构
- C 通过参考分配 /数组传递内存
- 使用字符串或字符动态分配(数组)