簡體   English   中英

使用 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;
    };
};

其中ArrayJsonValue數組的包裝器, Stringchar*EmbedStringchar[14]Numberintunsigned intdouble並集, 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調用數組成員的析構函數。 並且移動可簡單復制類型的數組的元素應該編譯為只是復制字節。

此外,我建議去掉這些StringEmbedString類,只使用std::string 至少,在我看來EmbedString的唯一目的是手動執行小字符串優化。 任何有價值的std::string實現都已經在幕后做到了。 請注意, std::string不能保證(並且通常不會)可以簡單地復制。 因此,您不能簡單地用std::string替換StringEmbedString而保留當前實現的其余部分。

如果您可以使用 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM