结构中的结构数组 - 指针类型是什么?

时间:2010-10-24 18:00:31

标签: c++ c

我在由perl脚本生成的文件中(在编译期间)中有以下声明:

 struct _gamedata
{

 short res_count;
 struct
 {
  void * resptr;
  short id;
  short type;
 } res_table[3];
}
_gamecoderes =
{
 3,
 {
  { &char_resource_ID_RES_welcome_object_ID,1002, 1001 },
  { &blah_resource_ID_RES_another_object_ID,1004, 1003 },
{ &char_resource_ID_RES_someting_object_ID,8019, 1001 },
 }
};

我的问题是在编译期间生成了struct _gamedatares_table中的项目数会有所不同。所以我不能提前提前声明res_table的大小。

我需要解析这个结构的一个实例,最初我是通过一个指向char的指针(而不是将struct _gamedata定义为一个类型。)但我定义的是res_table

e.g。

char * pb = (char *)_gamecoderes; 
                           // i.e. pb points to the instance of `struct _gamedata`.
short res_count = (short *)pb;
pb+=2;
res_table * entry = (res_table *)pb;
for( int i = 0; i < res_count; i++ )
{
    do_something_with_entry(*entry);
}

我的结果很糟糕。我不知道如何声明类型_struct gamedata,因为我需要能够在编译时处理res_table的变量长度。

4 个答案:

答案 0 :(得分:7)

由于结构是匿名的,因此无法引用此结构的类型。 (res_table只是成员名称,而不是类型名称)。您应该为结构提供名称:

struct GameResult {
        short type;
        short id;
        void* resptr;
};

struct _gamedata {
        short res_count;
        GameResult res_table[3];
};

此外,您不应将data投射到char*。可以使用the -> operator提取res_countentry。这样可以正确计算成员偏移量。

_gamedata* data = ...;
short res_count = data->res_count;
GameResult* entry = data->res_table;

或简单地说:

_gamedata* data;
for (int i = 0; i < data->res_count; ++ i)
   do_something_with_entry(data->res_table[i]);

答案 1 :(得分:3)

你的问题是对齐的。 res_countres_table之间至少会有两个字节的填充,因此您不能简单地将两个字节添加到pb。获取res_table指针的正确方法是:

res_table *table = &data->res_table;

如果您坚持要转向char*并返回,则必须使用offsetof

#include <stddef.h>
...
res_table *table = (res_table *) (pb + offsetof(_gamedata, res_table));

注意:在C ++中,您可能不会将offsetof与“非POD”数据类型一起使用(大约“您在普通C中无法声明的类型”)。正确的习语 - 无需投射到char*并返回 - 无论如何都可以。

答案 2 :(得分:2)

正如@Zack指出的那样,你的结构元素之间存在填充。

我假设您有一个char *因为您已经序列化了结构(在缓存中,在磁盘上或通过网络)。仅仅因为你开始使用char *并不意味着你必须以艰难的方式访问整个结构。将其转换为类型指针,让编译器为您完成工作:

_gamedata * data = (_gamedata *) my_char_pointer;
for( int i = 0; i < data->res_count; i++ )
{
    do_something_with_entry(*data->res_table[i]);
}

答案 3 :(得分:2)

理想情况下使用memcpy(3),至少使用类型_gamedata,或定义协议

我们可以考虑两个用例。我称之为程序员-API类型,序列化是一种内部便利,记录格式由编译器和库决定。在更正式定义和防弹的实现中,定义了一个协议,并编写了一个专用库来便携地读写一个流。

最佳实践将根据创建版本化协议和开发流I / O操作是否有意义而有所不同。

API

从编译器 - 对象序列化流中读取时,最好和最完全可移植的实现是声明或动态分配精确或最大大小的_gamedata,然后使用memcpy(3)将数据拉出串行流或设备内存或其他任何内容。这使编译器可以分配编译器代码访问的对象,并允许开发人员分配开发人员访问的对象(即char *)逻辑。

但至少要设置指向_gamedata的指针,编译器会为你做一切。另请注意,无论res_table[n]数组的大小如何,res_table[]始终位于“正确”地址。这不像是让它更大改变第一个元素的位置。

一般序列化最佳实践

如果_gamedata对象本身位于缓冲区中并且可能未对齐,例如,如果它不是编译器为_gamedata类型分配的对象,或者是真实的动态分配器,那么你仍然有潜在的对齐问题,唯一正确的解决方案是memcpy(3)缓冲区中的每个离散类型。

一个典型的错误是无论如何都要使用未对齐的指针,因为它在x86上工作(缓慢)。但是,在内核模式下或启用高级优化时,它可能无法在移动设备或未来架构上运行,也可能无法在某些架构上运行。最好坚持使用真正的C99。

这是一个协议

最后,当以任何方式序列化二进制数据时,您实际上是定义协议。因此,为了获得最大的稳健性,请不要让编译器定义您的协议。由于你在C中,你通常可以离散地处理每个基本对象而不会降低速度。如果作者和读者都这样做,那么只有开发人员必须就协议达成一致,而不是开发人员和编译器以及构建团队,C99作者和Dennis M. Ritchie,可能还有其他人。