繁体   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