c ++以编程方式计算成员的内存布局

时间:2010-03-04 23:54:37

标签: c++

假设在一个程序中,我得到了:

class Foo {
  int x;
  double y;
  char z;
};

class Bar {
  Foo f1;
  int t;
  Foo f2;
};

int main() {
  Bar b;
  bar.f1.z = 'h';
  bar.f2.z = 'w';
  ... some crap setting value of b;
  FILE *f = fopen("dump", "wb"); // c-style file
  fwrite(&b, sizeof(Bar), 1, f);
}

假设在另一个程序中,我有:

int main() {
  File *f = fopen("dump", "rb");
  std::string Foo = "int x; double y; char z;";
  std::string Bar = "Foo f1; int t; Foo f2;";

  // now, given this is it possible to read out
  // the value of bar.f1.z and bar.f2.z set earlier?
}

我要问的是:   鉴于我有一个类的类型,我能弄清楚C ++是如何解决它的吗?

5 个答案:

答案 0 :(得分:4)

您需要研究“序列化”。有一个库, Boost Serialization ,人们一直在推荐。

FWIW,我建议不要在类,结构和联合上使用fwritestd::ostream::write。允许编译器在成员之间插入填充,因此可能会写出垃圾。此外,指针不能很好地序列化。

要回答您的问题,为了确定从哪个结构加载数据,您需要某种 sentinel 来指示对象类型。这可以是从enum到对象名称的任何内容。

同时调查Factory设计模式。

答案 1 :(得分:3)

我不太确定你在问什么,所以我会跳跃......

如果您确实需要确定字段在结构中的位置,请使用offsetof

请注意链接页面中的“POD”限制。这是一个C宏,由于兼容性原因包含在C ++中。我们应该使用成员指针代替这些天,虽然成员指针不能解决所有相同的问题。

“offsetof”基本上是在地址零处想象你的struct的一个实例,然后查看你感兴趣的字段的地址。如果你的struct / class使用多个或虚拟继承,那就非常错了,因为找到了然后,字段涉及(通常)虚拟表中的检查。由于地址为零的虚构实例不存在,因此它没有虚拟表指针,因此您可能会遇到某种访问冲突崩溃。

有些编译器可以应对这种情况,因为它们已经用一个知道结构布局的内在函数替换了传统的offsetof宏,而没有尝试做虚构实例的技巧。即便如此,最好不要依赖它。

对于POD结构,offsetof是查找特定字段偏移的便捷方式,也是一种安全的方法,它确定实际偏移量,而不管平台应用的对齐方式。

对于字段的大小,您显然只使用sizeof。这只会留下特定于平台的问题 - 由于对齐而导致不同平台上的不同布局等,endianness等等; - )

修改

可能是一个愚蠢的问题,但是为什么不直接将文件中的数据直接转换到结构的实例中,基本上用fwrite执行的操作反过来呢?

您遇到与上述相同的可移植性问题,这意味着如果使用不同的选项,不同的编译器或不同的平台重新编译,您的代码可能无法读取自己的文件。但对于单平台应用程序,这种方式非常有效。

答案 2 :(得分:1)

您不能假设代表Bar的字节顺序。如果文件跨越系统或该程序使用不同的标志编译,那么您将以不同的顺序读取和写入。

我已经看到了解决这个问题的方法,但它可能仅适用于非常简单的类型。

我引用了raknet教程:

#pragma pack(push, 1)
struct structName
{
  unsigned char typeId; // Your type here
  // Your data here
};
#pragma pack(pop)

注意到#pragma pack(push,1)和#pragma pack(pop)?这些强制您的编译器(在本例中为VC ++)将结构打包为字节对齐。查看编译器文档以了解更多信息。

您想要序列化。

答案 3 :(得分:0)

对于您给出的示例,看起来您确实需要某种C解析器,它将使用您的类型声明来解析字符串。然后,您将能够以正确的方式解释从文件中读取的字节。

C中的结构按照声明的顺序列出成员。编译器可以根据特定于平台的对齐需求在成员之间插入填充。变量的大小也是特定于平台的。

答案 4 :(得分:0)

如果您可以控制该类,则可以使用成员指针。你肯定可以这样做。问题是你是否 ......

class Metadata
{
public:
    virtual int getOffset() = 0;
};

template <typename THost, typename TField>
class TypedMetadata : Metadata
{
private:
    TField (THost::*memberPointer_);

    TypedMetadata(TField (THost::*memberPointer))
    {
        memberPointer_ = memberPointer;
    }

public:
    static Metadata* getInstance(TField (THost::*memberPointer))
    {
        return new TypedMetadata<THost, TField>(memberPointer);
    }

    virtual int getOffset()
    {
        THost* host = 0;

        int result = (int)&(host->*memberPointer_);

        return result;
    }
};

template<typename THost, typename TField>
Metadata* getTypeMetadata(TField (THost::*memberPointer))
{
    return TypedMetadata<THost, TField>::getInstance(memberPointer);
}

class Contained
{
    char foo[47];
};

class Container
{
private:
    int x;
    int y;
    Contained contained;
    char c1;
    char* z;
    char c2;

public:
    static Metadata** getMetadata()
    {
        Metadata** metadata = new Metadata*[6];

        metadata[0] = getTypeMetadata(&Container::x);
        metadata[1] = getTypeMetadata(&Container::y);
        metadata[2] = getTypeMetadata(&Container::contained);
        metadata[3] = getTypeMetadata(&Container::c1);
        metadata[4] = getTypeMetadata(&Container::z);
        metadata[5] = getTypeMetadata(&Container::c2);

        return metadata;
    }
};

int main(array<System::String ^> ^args)
{
    Metadata** metadata = Container::getMetadata();

    std::cout << metadata[0]->getOffset() << std::endl;
    std::cout << metadata[1]->getOffset() << std::endl;
    std::cout << metadata[2]->getOffset() << std::endl;
    std::cout << metadata[3]->getOffset() << std::endl;
    std::cout << metadata[4]->getOffset() << std::endl;
    std::cout << metadata[5]->getOffset() << std::endl;

    return 0;
}