4

How do the two implementations differ:

struct queue {
    int a;
    int b;
    q_info *array;
};

and

struct queue {
    int a;
    int b;
    q_info array[0];
};
Stephane Rolland
  • 38,876
  • 35
  • 121
  • 169
AbhinavChoudhury
  • 1,167
  • 1
  • 18
  • 38
  • 4
    what clever thing do you want to do with `q_info array[0];` ? – Stephane Rolland Oct 10 '13 at 10:44
  • 3
    They differ in that `T array[0]` is undefined behavior. –  Oct 10 '13 at 10:47
  • 4
    Worth noting that [0] is non-standard (although supported by gcc and msvc). As i recall, C99 allowed usage of `some_type some_array[];` for the same purpose. Before this, it was usually achieved by using [1], and manipulating with `sizeof` later. – keltar Oct 10 '13 at 10:49
  • @H2CO3 standard explicitly allows only greater-than-zero (6.7.5.2), so it isn't. If compiler supports this - it's an extension and still well-defined. – keltar Oct 10 '13 at 10:59
  • @keltar Indeed, 6.7.5.2:1 is in a “Constraints” section, so a standard-conforming compiler is required to issue a diagnostic, but what happens after that is undefined behavior (and, by the way, GCC does not issue a diagnostic). – Pascal Cuoq Oct 10 '13 at 11:56
  • @PascalCuoq because zero-sized arrays is GNU C extension. Try it with -std=c99 -pedantic – keltar Oct 10 '13 at 11:59
  • @keltar At least [0] clearly indicates that some magic is expected. The standard-compliant declaration [1] where a flexible array member is intended is usually followed by undefined behavior later in the use of the struct: http://blog.frama-c.com/index.php?post/2013/07/31/From-Pascal-strings-to-Python-tuples – Pascal Cuoq Oct 10 '13 at 12:04
  • @PascalCuoq so please give a better solution. Espetially for msvc, which still have no support for C99. This came out of necessity. [0] or [] changes sizeof, it's hard to implement as macro. – keltar Oct 10 '13 at 12:40
  • @keltar I do not know of any solution that preserves compatibility with both a hypothetical modern compiler that would aggressively optimize anything undefined and with MSVC. Fortunately modern compilers aren't very good at UB-based optimizations yet, but their makers have repeatedly made their intentions clear in the last decade. – Pascal Cuoq Oct 10 '13 at 13:37
  • @keltar [this](http://stackoverflow.com/questions/18310789/conforming-variant-of-the-old-struct-hack). I ended up creating an array of unions. –  Oct 10 '13 at 14:19
  • @PascalCuoq I recommend [this](http://stackoverflow.com/questions/18310789/conforming-variant-of-the-old-struct-hack) to you too. –  Oct 10 '13 at 14:20
  • @H2CO3 Regarding the tangential discussion below a deleted answer there, C99 7.20.3:1 says “The pointer returned if the allocation succeeds is suitably aligned so that it may be assigned to a pointer to any type of object” about allocation functions including `malloc()`. – Pascal Cuoq Oct 10 '13 at 14:57
  • @PascalCuoq Yes, I then realized, thanks. (So I don't think that the deleted answer was correct -- still not sure about the conformance of the mutated struct hack, though.) –  Oct 10 '13 at 15:39
  • @H2CO3 out of curiosity - have you ever found a combination of compiler/flags that breaks 'classic' approach? If not - it's hardly any better. – keltar Oct 10 '13 at 18:25
  • @keltar No, I haven't, but I'd rather not rely on "but it will work anyway" (especially since I used that in a - say "infrastructure" - project that should be highly portable and reliable). In fact, I'd be happy to see the struct hack being broken with some compiler. –  Oct 10 '13 at 18:29
  • @H2CO3 maybe i haven't got answer in your link right, but doesn't it says it isn't guaranteed either? So how is it better, aside from reduced readability and forced manual initialisation? But anyway, my point is that e.g. by standard nullpointer isn't required to be all-0-bits, so memset(&somestruct, 0, sizeof(somestruct)) theoretically may be wrong if struct have pointers. But in this case, non-guaranteed memset is better for performance, readability, ease-of-use and maintainability reasons. I feel the same way for flexible array members - in theory they could be broken, but they won't. – keltar Oct 11 '13 at 03:16
  • @PascalCuoq: I've often thought that zero-sized arrays should have been allowed by the standard, so long as code doesn't access unallocated space. Given `struct S {int a[CONST_EXPR];} s,*p; int i;` a compiler would, if `CONST_EXPR==1`, be entitled to replace `s.a[i];` with `s[0]`. To ensure compatibility with a huge amount of existing code, it would be necessary to forbid such optimization when accessing `p->a[i]` (though I don't think the standard does), but if the standard had always allowed zero-sized arrays it could have said that *only* zero-sized arrays may be accessed beyond their size. – supercat Nov 08 '14 at 16:16

5 Answers5

11

The second struct does not use an array of zero elements - this is a pre-C99 trick for making flexible array members. The difference is that in the first snippet you need two mallocs - one for the struct, and one for the array, while in the second one you can do both in a single malloc:

size_t num_entries = 100;
struct queue *myQueue = malloc(sizeof(struct queue)+sizeof(q_info)*num_entries);

instead of

size_t num_entries = 100;
struct queue *myQueue = malloc(sizeof(struct queue));
myQueue->array = malloc(sizeof(q_info)*num_entries);

This lets you save on the number of deallocations, provides better locality of references, and also saves the space for one pointer.

Starting with C99 you can drop zero from the declaration of the array member:

struct queue {
    int a;
    int b;
    q_info array[];
};
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • The zero-sized array member was (unfortunately IMHO) never allowed by standard C; prior to C99 if one wanted to avoid Undefined Behavior it was necessary to use an array of size MAX_QUEUE_SIZE and allocate sizeof(queue)-sizeof(elementType)*(MAX_QUEUE_SIZE-num_entries) bytes. A lot of code has been written which uses a size of one and allocates sizeof(queue)+sizeof(elementType)*(num_entries-1) bytes, but that will sometimes allocate more space than needed, and is Undefined Behavior (though fortunately compilers will generally cause it to behave as intended). – supercat Nov 08 '14 at 16:24
2

These are completely different things:

  • The first contains a pointer to an external array.
  • the second is an inline array that happens to have zero elements.

The reason people do this is it's more space-efficient. You simply over-allocate the memory the struct needs, and then pretend the array has more elements then declared - the compiler won't mind (usually).

It also means you have one less pointer to dereference through, and you can allocate and free the memory for the struct and the array all in one.

Obviously, this trick only works when the array is the last element in the struct.

ams
  • 24,923
  • 4
  • 54
  • 75
1

In the first one there is actually a pointer allocated in struct queue, and sizeof(struct queue) == 2 * sizeof(int) + sizeof(q_info*)

In the second one there is no pointer or anything named array really exists in struct queue, and sizeof(struct queue) == 2 * sizeof(int). This is known as a trick to conveniently reference the data before or later using array. I've used this trick in implementing a memory allocator.

starrify
  • 14,307
  • 5
  • 33
  • 50
1

For the zero-sized array member, you can, when you allocate the structure, allocate more memory than the size of struct queue (for example malloc(sizeof(struct queue) + sizeof(q_info) * 10)) to have a contiguous area of memory you can use. Then the array will be part of that memory allocated, and for the example allocation you have ten q_info entries in it.

For the pointer, you have to make two allocations, one for the queue structure, and one for the array member. You of course have to call free twice, once for the array pointer and once for the structure.

However, once allocated both can be used the same.

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
1
q_info array[0];

decays to a pointer because of the automatic conversion. However, it is not assignable. You cannot say

array = <some address of an object>;

afterwards.

Hrishi
  • 7,110
  • 5
  • 27
  • 26