9

我想将一个类的填充字节设置为 0,因为我在字节级别保存/加载/比较/散列实例,并且垃圾初始化填充在每个操作中引入了不确定性。

我知道这将实现我想要的(对于可简单复制的类型):

struct Example
{
    Example(char a_, int b_)
    {
        memset(this, 0, sizeof(*this));
        a = a_;
        b = b_;
    }
    char a;
    int b;
};

我不喜欢这样做,有两个原因:我喜欢构造函数初始化列表,而且我知道将位设置为 0 并不总是与零初始化相同(例如,指针和浮点数不一定有零值都是 0 位)。

顺便说一句,它显然仅限于可简单复制的类型,但这对我来说不是问题,因为我上面列出的操作(在字节级别加载/保存/比较/散列)无论如何都需要可简单复制的类型。

我想要的是这样的[神奇]片段:

struct Example
{
    Example(char a_, int b_) : a(a_), b(b_)
    {
        // Leaves all members alone, and sets all padding bytes to 0.
        memset_only_padding_bytes(this, 0);
    }
    char a;
    int b;
};

我怀疑这样的事情是可能的,所以如果有人可以提出一个不丑陋的替代方案......我全神贯注:)

4

3 回答 3

7

我不知道在纯 C++ 中完全自动执行此操作。我们使用自定义代码生成系统来完成此任务(除其他外)。您可以使用一个宏来完成此操作,您将所有成员变量名称都提供给该宏;它只会在 offsetof(memberA)+sizeof(memberA) 和 offsetof(memberB) 之间寻找漏洞。

或者,以成员为基础进行序列化/散列,而不是作为二进制 blob。那是十种清洁剂。

哦,另一种选择——你可以提供一个operator new在返回之前明确清除内存的选项。不过,我不喜欢这种方法......它不适用于堆栈分配。

于 2013-10-23T15:13:51.590 回答
3

在二进制写入/读取它们时,您永远不应该使用填充结构。仅仅因为填充可能因平台而异,这将导致二进制不兼容。

使用一些编译器选项,例如在定义那些可写结构时#pragma pack (push, 1)禁用填充并使用.#pragma pack(pop)

可悲的是,这意味着您正在失去它提供的优化。如果这是一个问题,通过仔细设计您的结构,您可以通过插入虚拟变量来手动“填充”它们。然后零初始化变得很明显,您只需将零分配给那些假人。我不推荐“手动”方法,因为它很容易出错,但是当您使用二进制 blob 写入时,您可能已经担心了。但无论如何,之前都要对未填充的结构进行基准测试。

于 2013-10-23T15:25:21.733 回答
2

我遇到了类似的问题 - 只是说这是一个糟糕的设计决策(根据 dasblinkenlight 的评论)并不一定有帮助,因为您可能无法控制散列代码(在我的情况下,我使用的是外部库)。

一种解决方案是为您的类编写一个自定义迭代器,它遍历数据的字节并跳过填充。然后修改散列算法以使用自定义迭代器而不是指针。一种简单的方法是将指针模板化,以便它可以采用迭代器 - 由于指针和迭代器的语义相同,因此您不必修改模板化之外的任何代码。

编辑:Boost 提供了一个很好的库,可以很容易地将自定义迭代器添加到您的容器中:Boost.Iterator

无论您采用哪种解决方案,最好避免对填充进行散列处理,因为这样做意味着您的散列算法与您的数据结构高度耦合。如果您切换数据结构(或如 Agent_L 所提到的,在不同的平台上使用相同的数据结构以不同的方式填充),那么它将产生不同的哈希值。另一方面,如果您只对实际数据本身进行哈希处理,那么无论您以后使用何种数据结构,您将始终产生相同的哈希值。

于 2013-10-23T15:29:35.387 回答