0-initialized array memory footprint

A

anish singh

int main()
{
struct snd_widget{
int x;
};
struct snd_list {
int num_widgets;
struct snd_widget *widgets[0];
};
printf("%d\n", sizeof(struct snd_list));
return 0;
}

output:4 why?

I mean if it allocates memory for only int then when
we access 'widgets' how does it know that it is there?

Why I see only 4 as the size of snd_list?
 
N

Noob

anish said:
int main()
{
struct snd_widget{
int x;
};
struct snd_list {
int num_widgets;
struct snd_widget *widgets[0];
};
printf("%d\n", sizeof(struct snd_list));
return 0;
}

output:4 why?

I mean if it allocates memory for only int then when
we access 'widgets' how does it know that it is there?

Why I see only 4 as the size of snd_list?

What size would you expect an array of zero elements to be?

By the way, "The expression that specifies the size of an array
shall be an integral constant expression that has a value greater
than zero."

Related FAQ:
http://c-faq.com/struct/structhack.html

Regards.
 
J

James Kuyper

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.
 
S

Seebs

int main()
{
struct snd_widget{
int x;
};
struct snd_list {
int num_widgets;
struct snd_widget *widgets[0];
};
printf("%d\n", sizeof(struct snd_list));
return 0;
}
output:4 why?

There's several errors in this code, and you will get a better
understanding of the code if you fix the errors.
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.
Why I see only 4 as the size of snd_list?

How much space do you think a "struct snd_widget *" should take?

How much space do you think an array of N items of size X should take?

Can you express that as a general formula in terms of N and X?

If so, what would you expect that formula to produce for N=0?

(Also, try compiling with warnings enabled.)

-s
 
G

glen herrmannsfeldt

James Kuyper said:
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.
(snip)
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)
(snip)

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.


Yes, I remember using it in the late K&R and early ANSI C days.

I used to use the Metawindows package for graphics on a variety of
display devices on MS-DOS systems. They used many structures with
an array at the end like this. You could either subtract one when
computing the amount to allocate, or just waste one array element.

I don't remember now which one I usually did.

-- glen
 
A

anish singh

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

}
Just a demo code to confirm my understanding:

#include <stdlib.h>
#include <stdio.h>
#define NUM_WIDGET 10
int main()
{
int i = 0;
struct snd_widget{
int x;
char c;
short d;
}widget;
struct snd_list {
int num_widgets;
struct snd_widget *widgets[0];
}*list;
widget.x = 40;
widget.c = 'a';
widget.d = 40;
list = malloc(sizeof(*list) + NUM_WIDGET*sizeof list->widgets[0]);
if(list) {
list->num_widgets = NUM_WIDGET;
for(i=0;i<list->num_widgets;i++)
list->widgets = &widget;
for(i=0;i<list->num_widgets;i++)
printf("%d %c %d\n", list->widgets->x, list->widgets->c, list->widgets->d);
}
return 0;
}
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.

Can you please explain this with some test code?I am more than thankful.
 
J

James Kuyper

#include <stdlib.h>
#include <stdio.h>
#define NUM_WIDGET 10
int main()
{
int i = 0;
struct snd_widget{
int x;
char c;
short d;
}widget;
struct snd_list {
int num_widgets;
struct snd_widget *widgets[0];

Using 0 in that declaration is a constraint violation. That your
compiler accepts that declaration without generating a diagnostic
message means it supports a non-conforming extension to C.

If you want to do this with well-defined behavior under C99 or C2011,
you have to remove the 0, to make it a "flexible array member".

struct snd_widget *widgets[];
}*list;
widget.x = 40;
widget.c = 'a';
widget.d = 40;
list = malloc(sizeof(*list) + NUM_WIDGET*sizeof list->widgets[0]);

If NUM_WIDGET is actually constant, you would be better off avoiding all
of these complications, and simply declaring

struct snd_widget *widgets[NUM_WIDGET];

I hope that the constancy of NUM_WIDGET is just a a simplification?
if(list) {
list->num_widgets = NUM_WIDGET;
for(i=0;i<list->num_widgets;i++)
list->widgets = &widget;
for(i=0;i<list->num_widgets;i++)
printf("%d %c %d\n", list->widgets->x, list->widgets->c, list->widgets->d);
}
return 0;
} ....
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.

Can you please explain this with some test code?


The struct hack looks very similar to the code above, except that it has

struct snd_widget *widgets[1];

and

list = malloc(sizeof(*list) +
(NUM_WIDGET-1)*sizeof list->widgets[0]);

When using the struct hack, the statement

list->widgets = &widgets;

has undefined behavior for i>0, because widgets was declared as an array
of length 1. In principle, this is pretty bad; however, as a practical
matter, the actual behavior of (almost?) all real world implementations
of C90 was essentially equivalent to that of C99's flexible array members.
 
A

anish singh

Just a demo code to confirm my understanding:

#include <stdlib.h>
#include <stdio.h>
#define NUM_WIDGET 10
int main()

int i = 0;
struct snd_widget{
short d;

struct snd_list {
int num_widgets;
struct snd_widget *widgets[0];



Using 0 in that declaration is a constraint violation. That your

compiler accepts that declaration without generating a diagnostic

message means it supports a non-conforming extension to C.



If you want to do this with well-defined behavior under C99 or C2011, I use gnu c.

you have to remove the 0, to make it a "flexible array member".



struct snd_widget *widgets[];


widget.x = 40;
widget.c = 'a';
widget.d = 40;
list = malloc(sizeof(*list) + NUM_WIDGET*sizeof list->widgets[0]);



If NUM_WIDGET is actually constant, you would be better off avoiding all

of these complications, and simply declaring



struct snd_widget *widgets[NUM_WIDGET];



I hope that the constancy of NUM_WIDGET is just a a simplification? Just to make my point.


if(list) {
list->num_widgets = NUM_WIDGET;
for(i=0;i<list->num_widgets;i++)

list->widgets = &widget;
for(i=0;i<list->num_widgets;i++)

printf("%d %c %d\n", list->widgets->x, list->widgets->c, list->widgets->d);

return 0;

...
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.

Can you please explain this with some test code?



The struct hack looks very similar to the code above, except that it has



struct snd_widget *widgets[1];



and



list = malloc(sizeof(*list) +

(NUM_WIDGET-1)*sizeof list->widgets[0]);



When using the struct hack, the statement



list->widgets = &widgets;



has undefined behavior for i>0, because widgets was declared as an array

of length 1. In principle, this is pretty bad; however, as a practical

matter, the actual behavior of (almost?) all real world implementations

of C90 was essentially equivalent to that of C99's flexible array members.

Thanks.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,764
Messages
2,569,564
Members
45,039
Latest member
CasimiraVa

Latest Threads

Top