int main()
{
struct snd_widget{
int x;
};
struct snd_list {
int num_widgets;
struct snd_widget *widgets[0];
};
In the declaration of an array, "... the [ and ] may delimit
an expression ... If the expression is a constant expression, it shall
have a value greater than zero." 6.7.6.2p1, so your code violates a
constraint.
If your compiler accepted that code without generating a diagnostic
message, it's not fully conforming, at in that mode. Most C compilers
have a mode which is fully conform, but go into that mode only if you
turn on certain options (such as -ansi). Permitting a 0 in that context
is a common non-conforming extension to C. It's used to implement what's
called the struct hack, which was originally implemented using a length
of 1. However, the standard-conforming way to do the same thing is to
use what's called a "flexible array member".
"... the last element of a structure with more than one named member may
have an incomplete array type; this is called a flexible array member."
(6.7.2.1p18).
All you need to do to make widgets a flexible array member is to remove
the 0, turning it into an incomplete array type.
printf("%d\n", sizeof(struct snd_list));
return 0;
}
output:4 why?
The common extension to C that allows you to specify an length of 0 will
often (but not necessarily - it is, after all, an extension) have pretty
much the same semantics as a flexible array member: "In particular, the
size of the structure is as if the flexible array member were omitted
except that it may have more trailing padding than
the omission would imply." (6.7.6.2p18)
In other words, the standard requires sizeof(struct snd_list) >=
sizeof(int). Your comments imply that sizeof(int)==4 on your system.
I mean if it allocates memory for only int then when
we access 'widgets' how does it know that it is there?
It doesn't know. It's your responsibility, and NOT the compiler's, to
make sure that there's enough space. "... when a . (or ->) operator has
a left operand that is (a pointer to) a structure with a flexible array
member and the right operand names that member, it behaves as if that
member were replaced with the longest array (with the same element type)
that would not make the structure larger than the object being
accessed;" (6.7.6.2p18)
So how to you arrange that the "object being accessed" is long enough?
You can't do so using objects with automatic, static, or thread_local
storage duration. However, you can do so for objects with allocated
storage duration, by allocating enough memory:
struct snd_list *mylist =
malloc(sizeof *mylist + num_widgets * sizeof mylist->widgets[0]);
if(mylist)
{
mylist->num_widgets = num_widgets;
// Code using mylist
}
With this allocation, the memory I allocated is long enough to allow
widgets to have a length of num_widgets. Therefore, mylist->widgets
behaves as if it had been declared with that length. Note: if
num_widgets is 0, "it behaves as if it had one element but the behavior
is undefined if any attempt is made to access that element or to
generate a pointer one past it."
With the traditional struct hack, sizeof((*mylist) would include space
for widgets to have a length of 1. Therefore, you would instead use
offsetof(struct snd_list, widgets). Using the struct hack to access
mylist->widgets
for i>0 would have undefined behavior; but before
flexible array members were invented, it was the best available
alternative, and worked on (almost?) all implementations of C90.