C++编程实现成员的内存布局
假设在一个程序中,我得到:C++编程实现成员的内存布局,c++,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
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++是如何编排的吗?你需要研究序列化。人们一直在推荐一个图书馆,Boost Serialization FWIW,我建议不要在类、结构和联合上使用fwrite或std::ostream::write。允许编译器在成员之间插入填充,因此可能会写出垃圾。此外,指针的序列化也不是很好 为了回答您的问题,为了确定从哪个结构加载数据,您需要某种类型的哨兵来指示对象类型。这可以是从枚举到对象名称的任何内容
还要研究工厂设计模式。您不能假设表示条的字节顺序。如果文件穿过系统,或者该程序使用不同的标志编译,那么您将以不同的顺序进行读取和写入 我已经找到了解决这个问题的方法,但它可能只适用于非常简单的类型 我引用了raknet的一篇教程:
#pragma pack(push, 1)
struct structName
{
unsigned char typeId; // Your type here
// Your data here
};
#pragma pack(pop)
注意到布拉格玛填料函,1和布拉格玛填料函了吗?这些强制编译器(在本例中为VC++)将结构打包为字节对齐。查看编译器文档以了解更多信息
你想要序列化。我不太清楚你在问什么,所以我要跳过一步 如果确实需要确定字段在结构中的位置,请使用 请注意链接页面中的POD限制。这是一个C宏,包含在C++中,用于兼容性的原因。现在我们应该使用成员指针,尽管成员指针不能解决所有相同的问题 offsetof基本上是在地址0处想象一个结构实例,然后查看您感兴趣的字段的地址。如果您的结构/类使用多重继承或虚拟继承,则会出现可怕的错误,因为查找字段通常需要在虚拟表中进行检查。由于地址为零的虚拟实例不存在,因此它没有虚拟表指针,因此您可能会遇到某种访问冲突崩溃 一些编译器可以解决这一问题,因为它们已经用一个内在函数替换了传统的offsetof宏,该函数知道结构的布局,而不必尝试进行假想的实例欺骗。即便如此,最好不要依赖于此 不过,对于POD结构,offsetof是查找特定字段偏移量的一种方便方法,也是一种安全的方法,因为它决定了实际偏移量,而与平台应用的对齐方式无关 对于sizeof字段,显然只需使用sizeof。这只会留下平台特定的问题——由于对齐,不同平台上的不同布局等- 编辑 这可能是一个愚蠢的问题,但为什么不直接将文件中的数据加载到struct的实例中,就像使用fwrite所做的那样,但反过来呢
您会遇到与上述相同的可移植性问题,这意味着如果使用不同的选项、不同的编译器或不同的平台重新编译代码,则代码可能无法读取自己的文件。但是对于一个单一平台的应用程序,这种方法非常有效。对于您给出的示例,看起来您确实需要某种C解析器,它可以用您的类型声明解析字符串。然后您就能够以正确的方式解释从文件中读取的字节
C中的结构是按声明顺序在成员之间排列的。编译器可以根据平台特定的对齐需要在成员之间插入填充。变量的大小也是特定于平台的。如果可以控制类,则可以使用成员指针。你绝对可以做到。问题是你是否应该
1以文本模式打开文件,并使用二进制输出函数fwrite。小心,这在某些平台上有效,但在其他平台上无效。2类的成员在默认情况下是私有的,您要么使用结构,要么将它们声明为公共的。@Arak+1用于指出错误;这更好吗?这是我用过的解决方案。如果你知道这种方法的局限性,这是最简单的方法。
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;
}