4

We can initialize a struct with zero length array as specified in the link:

Zero-Length.

I'm using the following structures:

typedef unsigned char UINT8;
typedef unsigned short UINT16;

typedef struct _CommandHeader
{
    UINT16 len;
    UINT8 payload[0];
} CommandHeader;

typedef struct _CmdXHeader
{
    UINT8 len;
    UINT8 payload[0];
} CmdXhHeader;

Now the CommandHeader.payload should point / contain to CmdXHeader struct. i.e. memory should look like:

 -------------------------------------------------------------
| CommandHeader.len | CmdXHeader.len | CmdXHeader.payload ....|
 -------------------------------------------------------------

I can easily malloc CmdXHeader / CommandHeader to customized length. But how to assign value to CmdXHeader payload or how to link a CmdXHeader object to the CommandHeader.payload?


My Solution

Thanks for all the reply. I solved it in the following way:

//Get the buffer for CmdXHeader:
size_t cmdXHeader_len = sizeof(CmdXHeader) + custom_len;
CmdXHeader* cmdXHeader = (CmdXHeader*) malloc(cmdXHeader_len);
//Get a temporary pointer and assign the data to it
UINT8* p;
p[0] = 1;
p[2] = 2;
.......

//Now copy the memory of p to cmdXHeader
memcopy(cmdHeader->payload, p, custom_len);

// allocate the buffer for CommandHeader
CommandHeader* commandHeader = (CommandHeader*) malloc (sizeof (CommandHeader) + cmdXHeader_len);

// populate the fields in commandHeader
commandHeader->len = custom_len;
memcpy(commandHeader->payload, cmdXHeader, cmdXHeader_len);

Now the commandHeader object have the desired memory and we can typecast with whatever way we want...

Abhirup Ghosh
  • 153
  • 1
  • 6

4 Answers4

9

A zero-length array at the end of a struct, or anywhere else, is actually illegal (more precisely a constraint violation) in standard C. It's a gcc-specific extension.

It's one of several forms of the "struct hack". A slightly more portable way to do it is to define an array of length 1 rather than 0.

Dennis Ritchie, creator of the C language, has called it "unwarranted chumminess with the C implementation".

The 1999 revision of the ISO C Standard introduced a feature called the "flexible array member", a more robust way to do this. Most modern C compilers support this feature (I suspect Microsoft's compiler doesn't, though).

This is discussed at length in question 2.6 of the comp.lang.c FAQ.

As for how you access it, whichever form you use, you can treat it like you'd treat any array. The name of the member decays to a pointer in most contexts, allowing you to index into it. As long as you've allocated enough memory, you can do things like:

CommandHeader *ch;
ch = malloc(computed_size);
if (ch == NULL) { /* allocation failed, bail out */ }
ch.len = 42;
ch.payload[0] = 10;
ch.payload[1] = 20;
/* ... */

Obviously this is only a rough outline.

Note that sizeof, when applied to the type CommandHeader or an object of that type, will give you a result that does not include the flexible array member.

Note also that identifiers starting with underscores are reserved to the implementation. You should never define such identifiers in your own code. There's no need to use distinct identifiers for the typedef name and the struct tag:

typedef struct CommandHeader
{
    UINT16 len;
    UINT8 payload[0];
} CommandHeader;

I'd also suggest using the standard types uint16_t and uint8_t, defined in <stdint.h> (assuming your compiler supports it; it's also new in C99).

(Actually the rules for identifiers starting with underscores are slightly more complex. Quoting N1570, the latest draft of the standard, section 7.1.3:

  • All identifiers that begin with an underscore and either an uppercase letter or another underscore are always reserved for any use.
  • All identifiers that begin with an underscore are always reserved for use as identifiers with file scope in both the ordinary and tag name spaces.

And there are several more classes of reserved identifiers.

But rather than working out which identifiers are safe to use at file scope and which are safe to use in other scopes, it's much easier just to avoid defining any identifiers that start with an underscore.)

Keith Thompson
  • 254,901
  • 44
  • 429
  • 631
  • +1 nice answer. Genuinely curious. I thought the the standard position on leading underscores was any identifier that start with either *two* leading underscores or a *single* leading underscore followed by an *uppercase* letter is reserved for the implementation. At least thats the way it is for C++ from 17.6.4.3.2. And well pointed out, the OP is violating the second of those conditions. But does C somehow restrict further to **all** underscore-leading global ids, including those with a single underscore and *lowercase*? – WhozCraig Jan 30 '13 at 19:07
  • @WhozCraig: I've added a more thorough explanation to my answer. – Keith Thompson Jan 30 '13 at 19:16
  • "Note that sizeof, when applied to the type CommandHeader or an object of that type, will give you a result that does not include the flexible array member." I think this reason alone is reason enough to NOT do this. – Josh Petitt Jan 31 '13 at 03:11
1

I assume you've got some bytes in memory and you want to find the pointer to payload?

typedef struct _CmdXHeader
{
    UINT8 len;
    UINT8* payload;
} CmdXhHeader;

typedef struct _CommandHeader
{
    UINT16 len;
    CmdXhHeader xhead;
} CommandHeader;

You could then cast your memory to a pointer to CommandHeader

uint8_t* my_binary_data = { /* assume you've got some data */ };

CommandHeader* cmdheader = (CommandHeader*) my_binary_data;

// access the data
cmdheader->xhead.payload[0];

IMPORTANT! Unless you pack your struct, it will probably align on word boundaries and not be portable. See your compiler docs for specific syntax on how to pack the struct.

Also, I'd only do what you've shown if you are consuming bytes (i.e. read from a file, or from a wire). IF you are the creator of the data, then I would heartily recommend against what you've shown.

Josh Petitt
  • 9,371
  • 12
  • 56
  • 104
0
   struct _CommandHeader *commandHeader = malloc(sizeof(struct _CommandHeader)+
                                  sizeof(struct _CmdXHeader));
sr01853
  • 6,043
  • 1
  • 19
  • 39
  • I've reformatted your code so it can be read wthout side-scrolling. But the cast is unnecessary and could potentially mask errors. As long as you have the required `#include `, you can assign the result of `malloc()` directly to a pointer; the `void*` result will be implicitly converted to the appropriate type. – Keith Thompson Jan 30 '13 at 19:19
  • Thanks Sibrajas for your comment. However, I'm trying to use the typical zero length array itself. I know if I change it to a pointer the things would be pretty much simple and can be used as you have specified. – Abhirup Ghosh Jan 31 '13 at 11:13
0

Better to use payload[] instead of payload[0] in C99. Some of C99 compilers discourage usage of zero length array. So, in case you get an error here :

typedef struct CommandHeader
{
    UINT16 len;
    UINT8 payload[0];
} CommandHeader;

you can always correct it as :

typedef struct CommandHeader
{
    UINT16 len;
    UINT8 payload[];
} CommandHeader;
NeilBlue
  • 173
  • 2
  • 9