10

Quoting from the C-std section 6.7.2.1,

struct s { int n; double d[]; };

This is a valid structure declaration. I am looking for some practical use of this kind of syntax. To be precise, how is this construct any more or less powerful than keeping a double* as the 2nd element? Or is this another case of 'you-can-do-it-in-multiple-ways'?

Arpan

M.M
  • 138,810
  • 21
  • 208
  • 365
Fanatic23
  • 3,378
  • 2
  • 28
  • 51

4 Answers4

16

The C FAQ answers precisely this question. The quick answer is that this structure will include the double array inside the structure rather than a pointer to an array outside the structure. As a quick example, you could use your structure as in this example:

struct s *mystruct = malloc(sizeof(struct s) + 5 * sizeof(double));
s->n = 12;
s->d[0] = 4.0;
s->d[1] = 5.0;
s->d[2] = 6.0;

And so on - the size of the array you care about is included in the allocation, and then you can use it just like any array. Normally such a type contains the size as part of the structure, since using the + trick to skip through an array of type s will be necessarily complicated by this situation.

To your added question 'how is this construct any more or less powerful than keeping a [pointer] as the 2nd element?', it's no more powerful per se, but you don't need to keep a pointer around, so you would save at least that much space - also when you are copying the structure, you would also copy the array, rather than a pointer to an array - a subtle difference sometimes, but very important other times. 'You-can-do-it-in-multiple-ways' is probably a good explanation, but there are cases where you would specifically want one design or the other.

Community
  • 1
  • 1
Carl Norum
  • 219,201
  • 40
  • 422
  • 469
  • so struct s s1 = malloc (...); and then struct s s2 = s1; would mean that s2 gets an array which is automagically created and contents of s1 copied? does the same hold if instead of POD types struct s has user defined class as 2nd element? – Fanatic23 Jun 15 '10 at 17:50
  • No, no magical copying would happen with structure assignment; but if you use `memcpy()` with the appropriate size it will work. If you have a pointer, you would need to allocate memory and copy the array separately. – Carl Norum Jun 15 '10 at 18:01
  • 1
    I'm not sure that that link to the C FAQ, q2.6, really answers this question at all. If it does, it is only in an arcane sense that would only make sense to someone that already knows the answer. In fact, the link suggests this, if it is talking about the same thing, is not to be seen as portable. – BobbyShaftoe Jun 15 '10 at 22:05
  • 1
    @Arpan: Your example isn't possible as written, because if `struct s` has a flexible array member, then the type is incomplete and you cannot declare a variable of that type (you can only declare pointers to it - `struct s *`). You can't change it to `struct s *s1 = malloc(); struct s *s2; *s2 = *s1;` either, because doing so still tries to access an incomplete type. None of these will compile. – caf Jun 16 '10 at 02:13
  • the `struct s { int n; double d[]; };` approach has the advantage that you get good cache locality between the int with the length of the array and the start of the actual array data. – Spudd86 Jun 16 '10 at 19:56
6

The primary advantage is that a flexible array member allows you to allocate a single block of memory for the array along with the other data in the struct (with a pointer, you'd typically end up with two separately allocated blocks).

It's also useful with data transmitted by quite a few network protocols, where the incoming stream is defined the same way -- an integer defining a length, followed by that many units (typically bytes/octets) of data. You can (typically) use a type-pun to overlay a struct with a flexible array member onto a buffer filled with such data, and work with it directly instead of having to parse it out into pieces and then work with the pieces individually.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • In my experience, implementing a network protocol (or a file format, which is essentially the same problem) by type-punning a buffer of bytes onto a struct type is usually a case of You're Doing It Wrong. Deserialising it field-by-field instead ends up being much more portable. – caf Jun 16 '10 at 02:16
  • @caf: Deserializing field by field is more portable, but type punning may in some cases allow code to be more readable and more efficient, especially if it can build a table of pointers to things stored within an existing buffer, rather than having to allocate space for a second copy of all the information and then copy all the information from the buffer-of-bytes into the newly-allocated space. What would have made things really portable would have been if C supported "explicit-layout" structures, so code could say, e.g. "I need a data type that's 64 bytes, can be located... – supercat Aug 31 '16 at 19:35
  • ...on any 2-byte boundary, and includes [among other things] a 32-bit integer called "Woozle" stored at offset 12 as four octets in little-endian order." Having a compiler support that sort of thing and handle it efficiently in cases where it coincides with a compiler's natural layout would be cheaper than trying to recognize and optimize all the different variations on `(((uint32_t)ptr[15] << 24) | ((uint32_t)ptr[14] << 16) | ((uint32_t)ptr[13] << 8) | ptr[12])` which could be replaced with a couple of 16-bit load from address ptr+12 and ptr+14, or a single 32-bit load from ptr+12. – supercat Aug 31 '16 at 19:38
5

You can use it to add header fields to dynamically allocated arrays, the most common one of which would be its size:

struct int_array
{
    size_t size;
    int values[];
};

struct int_array *foo = malloc(sizeof *foo + 42 * sizeof *foo->values);
foo->size = 42;

...

for(size_t i = 0; i < foo->size; ++i)
    foo->values[i] = i * i;

You could achieve similar results by using an int * member instead and allocating the array seperately, but it would be less efficient both in terms of memory (additional pointer, heap management for 2nd memory block) and runtime (additional indirection, 2nd allocation).

Christoph
  • 164,997
  • 36
  • 182
  • 240
2

I've seen this used on Windows for strings which are tagged by their length. The character data is stored directly after the length in memory, keeping everything together neatly.

typedef struct {
    SIZE_T bytes;
    TCHAR chars[];
} tagged_string;
zildjohn01
  • 11,339
  • 6
  • 52
  • 58