开发者

An array of structures within a structure - what's the pointer type?

I have the following declaration in a file that gets generated by a perl script ( during compilation ):

 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_som开发者_运维百科eting_object_ID,8019, 1001 },
 }
};

My problem is that struct _gamedata is generated during compile time and the number of items in res_table will vary. So I can't provide a type declaring the size of res_table in advance.

I need to parse an instance of this structure, originally I was doing this via a pointer to a char ( and not defining struct _gamedata as a type. But I am defining 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);
}

I'm getting wierd results with this. I'm not sure how to declare a type _struct gamedata as I need to be able to handle a variable length for res_table at compile time.


Since the struct is anonymous, there's no way to refer to the type of this struct. (res_table is just the member name, not the type's name). You should provide a name for the struct:

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

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

Also, you shouldn't cast the data to a char*. The res_count and entry's can be extracted using the -> operator. This way the member offsets can be computed correctly.

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

or simply:

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


Your problem is alignment. There will be at least two bytes of padding in between res_count and res_table, so you cannot simply add two to pb. The correct way to get a pointer to res_table is:

res_table *table = &data->res_table;

If you insist on casting to char* and back, you must use offsetof:

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

Note: in C++ you may not use offsetof with "non-POD" data types (approximately "types you could not have declared in plain C"). The correct idiom -- without casting to char* and back -- works either way.


Ideally use memcpy(3), at least use type _gamedata, or define a protocol

We can consider two use cases. In what I might call the programmer-API type, serialization is an internal convenience and the record format is determined by the compiler and library. In the more formally defined and bulletproof implementation, a protocol is defined and a special-purpose library is written to portably read and write a stream.

The best practice will differ depending on whether it makes sense to create a versioned protocol and develop stream I/O operations.

API

The best and most completely portable implementation when reading from compiler-oject serialized streams would be to declare or dynamically allocate an exact or max-sized _gamedata and then use memcpy(3) to pull the data out of the serial stream or device memory or whatever it is. This lets the compiler allocate the object that is accessed by compiler code and it lets the developer allocate the object that is accessed by developer (i.e., char *) logic.

But at a minimum, set a pointer to _gamedata and the compiler will do everything for you. Note also that res_table[n] will always be at the "right" address regardless of the size of the res_table[] array. It's not like making it bigger changes the location of the first element.

General serialization best practice

If the _gamedata object itself is in a buffer and potentially misaligned, i,e., if it is anything other than an object allocated for a _gamedata type by the compiler or dynamically by a real allocator, then you still have potential alignment issues and the only correct solution is to memcpy(3) each discrete type out of the buffer.

A typical error is to use the misaligned pointer anyway, because it works (slowly) on x86. But it may not work on mobile devices, or future architectures, or on some architectures when in kernel mode, or with advanced optimizations enabled. It's best to stick with real C99.

It's a protocol

Finally, when serializing binary data in any fashion you are really defining a protocol. So, for maximum robustness, don't let the compiler define your protocol. Since you are in C, you can generally handle each fundamental object discretely with no loss in speed. If both the writer and reader do it, then only the developers have to agree on the protocol, not the developers and the compilers and the build team, and the C99 authors, and Dennis M. Ritchie, and probably some others.


As @Zack points out, there is padding between elements of your structure.

I'm assuming you have a char* because you've serialized the structure (in a cache, on disk, or over the network). Just because you are starting with a char * doesn't mean you have to access the entire struct the hard way. Cast it to a typed pointer, and let the compiler do the work for you:

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

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜