[英]Using memset and memcpy correctly to initialize a character array in C++
[英]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
不能保證(並且通常不會)可以簡單地復制。 因此,您不能簡單地用std::string
替換String
和EmbedString
而保留當前實現的其余部分。
如果您可以使用 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
...
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.