内存清理程序错误:clang5 + msan +带有填充字节的结构的fwrite

时间:2018-01-26 08:48:20

标签: c++ clang++ memory-sanitizer

最小例子:

#include <fstream>

struct TFoo
{
    bool Field1_ = false;
    uint64_t Field2_ = 0;
};

int main() {
    TFoo Foo_{};
    const char* filename = "text.txt";
    std::ofstream f(filename);

    f.write((char*)(&Foo_), sizeof(Foo_));
}
自msan第5版以来,

clang报告的内容如下:

Uninitialized bytes in __interceptor_fwrite at offset 0 inside [0x720000000000, 15)
==71928==WARNING: MemorySanitizer: use-of-uninitialized-value
    #0 0x2823aa  (/home/<hidden>/test-ofstream+0x2823aa)
    #1 0x27830f  (/home/<hidden>/test-ofstream+0x27830f)
    #2 0x272757  (/home/<hidden>/test-ofstream+0x272757)
    #3 0x271388  (/home/<hidden>/test-ofstream+0x271388)
    #4 0x270c96  (/home/<hidden>/test-ofstream+0x270c96)
    #5 0x2709e2  (/home/<hidden>/test-ofstream+0x2709e2)
    #6 0x26ff9e  (/home/<hidden>/test-ofstream+0x26ff9e)
    #7 0x7fbc7238382f  (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)

这是因为Field1_Field2_之间的填充字节未初始化。

没关系,MSAN是对的。

但是如果代码中包含非常大的代码示例(将结构保存到二进制文件中),是否有任何漂亮的方法可以大规模地使代码更好?

(打包结构不是我们的解决方案。)

1 个答案:

答案 0 :(得分:1)

如果您可以更改struct TFoo的定义,则可以添加如下构造函数:

struct TFoo {
  bool Field1_;
  uint64_t Fields_;
  TFoo() { memset(this, 0, sizeof(*this)); }
  TFoo(const TFoo &rhs) : TFoo() { Field1_ = rhs.Field1_; Field2_ = rhs.Field2_; }
};

我认为你不能按照这个标准使用memset,但它可能适用于你的编译器。请参阅How can I zero just the padding bytes of a class?,其中讨论了此解决方案。

原始回答

我想到了Field1_Field2_之间的填充字节清零。但老实说,我不确定它是否符合标准。当然,TFoo对象的某种序列化会好得多,但如果我理解正确,它不适合你,是吗?

struct TFoo
{
  bool Field1_ = false;
  uint64_t Field2_ = 0;
}; 

struct TFooWrapper {
  union {
    TFoo tfoo;
    char dummy[sizeof(TFoo)] = { 0 };
  } u;
};

<强>更新

来自http://en.cppreference.com/w/cpp/language/union最多只有一个变体成员可以拥有默认成员初始化程序。因此上述代码可能不正确。但是,您可以,例如,为TFooWrapper定义默认构造函数,将所有字节清零。