By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
458,217 Members | 1,397 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 458,217 IT Pros & Developers. It's quick & easy.

Flexible arrays - v confused

P: n/a
I thought (as they're in c99) that flexible arrays were there for a
number of reasons - but I also thought they'd be great for stepping
into structures (say) that were aligned in memory after (say) a header
struct, e.g.:

struct element
{
int a;
int b;
};
typedef struct element elem;

struct header
{
int a;
elem element[];
};
typedef struct header head;
....

head * p;
....

(p -> element)[10] = ...;

However, the same 'trick' can be done if header contained this

elem * element;
};

So, I can't really see the benefit of using empty array notation here.
And anyway, how would you malloc some memory for element when it's an
array type - surely p -> element = malloc... is an error, as element is
an array, and so can't be assigned to? That leaves me thinking that to
get it to work like that, maybe you should malloc a lump of memory,
then cast the address to a header *, access element, and then index
into that. But again, you could do this using straight forward pointer
notation in the declaration of element?

So, then I thought, well, maybe the notation would allow me to
initialise a struct ...

struct foo
{
int a;
char b[];
};

struct foo s = {10, {'f', 'o', 'o'}};

But gcc doesn't like that (with -std=c99), and again, if b were
declared char * b, surely this is illegal ...

struct foo s = {10, {"foo"}};

So, Istarting from 'they must be great for a number of reasons' I'm now
totally confused as to what flexible arrays can do for you. Could
someone please help me?

x

Jo

Feb 14 '06 #1
Share this Question
Share on Google+
20 Replies


P: n/a
#include <stdio.h>
#include <stdlib.h>

struct element
{
int a;
int b;
};

typedef struct element elem;

struct header
{
int a;
elem element[];
};

typedef struct header head;

int main(int argc, char *argv[])
{
head *p;
p = malloc ( sizeof(struct header) + sizeof(struct element) * 10);
system("PAUSE");
return 0;
}
This declares a pointer p of type head
which allocates memory for one struct header and 10 elements
of type struct element.

Feb 14 '06 #2

P: n/a
Zero wrote:

This declares a pointer p of type head
which allocates memory for one struct header and 10 elements
of type struct element.


That's nice, so what? (See below).

Brian

--
Please quote enough of the previous message for context. To do so from
Google, click "show options" and use the Reply shown in the expanded
header.
Feb 14 '06 #3

P: n/a
In article <11*********************@g47g2000cwa.googlegroups. com>
<me*********@googlemail.com> wrote:
I thought (as they're in c99) that flexible arrays were there for a
number of reasons ...
They really exist for just one reason: to legitimize the old
"struct hack" (as it is called in the FAQ).

To review for a moment, the "struct hack" is done something like this:

#include <stdlib.h>

struct vector {
size_t n; /* number of values in the vector */
double val[1]; /* actually size n */
};

struct vector *vec_new(size_t n) {
struct vector *p;

/* need n-1 here because the array has an "extra" element */
p = malloc(sizeof *p + (n ? n - 1 : 0) * sizeof p->val[0]);
if (p != NULL) {
p->n = n;
while (n)
p->val[--n] = 0.0;
}
return p;
}

void vec_free(struct vector *p) {
free(p);
}

double vec_access(struct vector *p, size_t i) {
if (i >= p->n)
panic("vec_access: nonexistent element %lu\n", (unsigned long)i);
return p->val[i];
}

To use a C99 "flexible array" structure member, we simply remove
the constant "1" and get rid of the code that subtracts 1 from the
computation passed to malloc(). This simplifies vec_new:

p = malloc(sizeof *p + n * sizeof p->val[0]);

and otherwise has no effect on the machine-level code produced,
except that it *must* actually work, whereas the pre-C99 version
was allowed to produce code that misbehaved at runtime. (That is,
the "struct hack" is not 100% legitimate, although no one has
produced examples of machines on which it does not work.)
However, the same 'trick' can be done if [the struct that has the
Flexible Array Member] contained [a pointer] ...
In other words, we could equally write:

struct vector {
size_t n;
double *val;
};

struct vector *vec_new(size_t n) {
struct vector *p;

p = malloc(sizeof *p);
if (p != NULL) {
p->val = malloc(n * sizeof *p->val);
if (p->val != NULL) {
p->n = n;
while (n)
p->val[--n] = 0.0;
} else {
free(p);
p = NULL;
}
}
return p;
}

void vec_free(struct vector *p) {
free(p->val); /* or perhaps check p!=NULL first */
free(p);
}

The rest of the code remains unchanged.
So, I can't really see the benefit of using empty array notation here.
The advantage to using the F.A.M. (or the struct hack) is that we
avoid the second malloc() call and the associated extra source code,
and the underlying machine code tends to be more efficient as well
(although the latter is certainly never promised).
And anyway, how would you malloc some memory for element when it's an
array type ...
As shown above.
So, then I thought, well, maybe the notation would allow me to
initialise a struct ...
There is a way to map the F.A.M. or struct hack onto an actual,
initialized instance. This is at least "technically iffy", however,
as there is no guarantee that the offets of the members of the
"inflexible" structure match those of the "flexible" structure.
struct foo
{
int a;
char b[];
};

struct foo s = {10, {'f', 'o', 'o'}};


You cannot do this; but you can do:

struct foo {
int a;
char b[];
};
struct foo_with_b_size_3 {
int a;
char b[3];
} s = { 10, { 'f', 'o', 'o' } };

struct foo *get_s(void) {
return (struct foo *)&s;
}

This runs into that "technically iffy" problem (although, again,
it seems to work on all real implementations). The fact that it
requires a pointer cast is reason enough to be suspicious: any code
that requires a pointer cast is probably skating on thin ice.
--
In-Real-Life: Chris Torek, Wind River Systems
Salt Lake City, UT, USA (4039.22'N, 11150.29'W) +1 801 277 2603
email: forget about it http://web.torek.net/torek/index.html
Reading email is like searching for food in the garbage, thanks to spammers.
Feb 14 '06 #4

P: n/a

So, I can't really see the benefit of using empty array notation here.
And anyway, how would you malloc some memory for element when it's an
array type - surely p -> element = malloc... is an error, as element is
an array, and so can't be assigned to? That leaves me thinking that to
get it to work like that, maybe you should malloc a lump of memory,
then cast the address to a header *, access element, and then index
into that. But again, you could do this using straight forward pointer
notation in the declaration of element?


The difference is that with a pointer the struct would have a data layout
like
int, elem*
where the pointer would point to an array of elem. With flexible arrays the
data layout looks like
int, elem[0], elem[1], etc
where you allocates the struct and the flexible array all in one block.
Think of the flexible array as if there were a fixed size like elem
element[10] and then allow the "10" to be defined at run time. The other
reply gave an example malloc call.
Feb 14 '06 #5

P: n/a

Chris Torek wrote:
In article <11*********************@g47g2000cwa.googlegroups. com>
<me*********@googlemail.com> wrote:
I thought (as they're in c99) that flexible arrays were there for a
number of reasons ...


They really exist for just one reason: to legitimize the old
"struct hack" (as it is called in the FAQ).

To review for a moment, the "struct hack" is done something like this:

#include <stdlib.h>

struct vector {
size_t n; /* number of values in the vector */
double val[1]; /* actually size n */
};

struct vector *vec_new(size_t n) {
struct vector *p;

/* need n-1 here because the array has an "extra" element */
p if (p ! p->n while (n)
p->val[--n] }
return p;
}

void vec_free(struct vector *p) {
free(p);
}

double vec_access(struct vector *p, size_t i) {
if (i > panic("vec_access: nonexistent element %lu\n", (unsigned long)i);
return p->val[i];
}

To use a C99 "flexible array" structure member, we simply remove
the constant "1" and get rid of the code that subtracts 1 from the
computation passed to malloc(). This simplifies vec_new:

p
and otherwise has no effect on the machine-level code produced,
except that it *must* actually work, whereas the pre-C99 version
was allowed to produce code that misbehaved at runtime. (That is,
the "struct hack" is not 100% legitimate, although no one has
produced examples of machines on which it does not work.)
However, the same 'trick' can be done if [the struct that has the
Flexible Array Member] contained [a pointer] ...


In other words, we could equally write:

struct vector {
size_t n;
double *val;
};

struct vector *vec_new(size_t n) {
struct vector *p;

p if (p ! p->val if (p->val ! p->n while (n)
p->val[--n] } else {
free(p);
p }
}
return p;
}

void vec_free(struct vector *p) {
free(p->val); /* or perhaps check p!=NULL first */
free(p);
}

The rest of the code remains unchanged.
So, I can't really see the benefit of using empty array notation here.


The advantage to using the F.A.M. (or the struct hack) is that we
avoid the second malloc() call and the associated extra source code,
and the underlying machine code tends to be more efficient as well
(although the latter is certainly never promised).
And anyway, how would you malloc some memory for element when it's an
array type ...


As shown above.
So, then I thought, well, maybe the notation would allow me to
initialise a struct ...


There is a way to map the F.A.M. or struct hack onto an actual,
initialized instance. This is at least "technically iffy", however,
as there is no guarantee that the offets of the members of the
"inflexible" structure match those of the "flexible" structure.
struct foo
{
int a;
char b[];
};

struct foo s

You cannot do this; but you can do:

struct foo {
int a;
char b[];
};
struct foo_with_b_size_3 {
int a;
char b[3];
} s
struct foo *get_s(void) {
return (struct foo *)&s;
}

This runs into that "technically iffy" problem (although, again,
it seems to work on all real implementations). The fact that it
requires a pointer cast is reason enough to be suspicious: any code
that requires a pointer cast is probably skating on thin ice.
--


Thanks to all that replied. The veil has been lifted.

However, couple of questions about the code.

If you simply knocked this up to show the methods etc, what follows is
probably not too interesting, or worth commenting upon. However, there
was a couple of things that made me think, so I include them here to
see if there's any comment etc.

Although sizeof never executes its 'arg' (don't know if those are the
right terms, but I'm sure they're good enough to be understood), I'd
personally go with
sizeof(vector) --rather than-- sizeof *p

To perhaps calm the nerves of any maintenence programmer that see an
auto *p that *seems* to be /dereferenced/ ?

The former, of course, would need a typedef struct vector vector -
which I also think would make the code a little easier to read.

On vec_access, why not return a double * to allow both read and write
access, e.g., using return &(p->val[i]);

x

Jo

Feb 15 '06 #6

P: n/a
<me*********@googlemail.com> wrote in message
news:11**********************@g44g2000cwa.googlegr oups.com...
[snip]
Although sizeof never executes its 'arg' (don't know if those are the
right terms, but I'm sure they're good enough to be understood),
I think "evaluates" and "operand" are the terms you are looking for.
I'd personally go with
sizeof(vector) --rather than-- sizeof *p

To perhaps calm the nerves of any maintenence programmer that see an
auto *p that *seems* to be /dereferenced/ ?


If, perhaps as a maintenance programmer, I see the following line of code:

p = malloc(sizeof *p);

I immediately know that the call requests allocation of the right amount of
space for one of whatever p points to.

OTOH, if I see something like:

p = malloc(sizeof (struct vector));

I first have to remember or check that p has the correct type.

Alex
Feb 15 '06 #7

P: n/a

Alex Fraser wrote:
<me*********@googlemail.com> wrote in message
news:11**********************@g44g2000cwa.googlegr oups.com...
[snip]
Although sizeof never executes its 'arg' (don't know if those are the
right terms, but I'm sure they're good enough to be understood),


I think "evaluates" and "operand" are the terms you are looking for.
I'd personally go with
sizeof(vector) --rather than-- sizeof *p

To perhaps calm the nerves of any maintenence programmer that see an
auto *p that *seems* to be /dereferenced/ ?


If, perhaps as a maintenance programmer, I see the following line of code:

p = malloc(sizeof *p);

I immediately know that the call requests allocation of the right amount of
space for one of whatever p points to.

OTOH, if I see something like:

p = malloc(sizeof (struct vector));

I first have to remember or check that p has the correct type.


Hmmm, yes, that's a good observation, and reason for doing it. Thanks.

x

Jo

Feb 15 '06 #8

P: n/a
me*********@googlemail.com writes:
[...]
Although sizeof never executes its 'arg' (don't know if those are the
right terms, but I'm sure they're good enough to be understood), I'd
personally go with
sizeof(vector) --rather than-- sizeof *p

To perhaps calm the nerves of any maintenence programmer that see an
auto *p that *seems* to be /dereferenced/ ?
The operand of the sizeof operator is not evaluated *unless* the
operand's type is a variable length array (which isn't the case here).

Somebody else explained the reason for using sizeof *p, but I'll
expand on it a little.

Given:

some_type *ptr;
...
ptr = malloc(sizeof(some_type));

it's very easy to change the declaration to

some_other_type *ptr;

and forget to change the argument to the malloc() call. If you use
the recommended form:

ptr = malloc(sizeof *ptr);

that's not an issue.
The former, of course, would need a typedef struct vector vector -
which I also think would make the code a little easier to read.


No, a typedef isn't needed:

struct vector *ptr;
ptr = malloc(sizeof struct vector);

is perfectly legal. (And in my opinion, routinely using typedefs for
all structs is not useful; it creates two distinct names for each type
when one name, "struct vector" is quite sufficient.)

--
Keith Thompson (The_Other_Keith) ks***@mib.org <http://www.ghoti.net/~kst>
San Diego Supercomputer Center <*> <http://users.sdsc.edu/~kst>
We must do something. This is something. Therefore, we must do this.
Feb 15 '06 #9

P: n/a
Keith Thompson wrote:

struct vector *ptr;
ptr = malloc(sizeof struct vector);

is perfectly legal.


sizeof(struct vector)

When you are in the habit of omitting the brackets for "sizeof var",
it's easy to forget to include them for "sizeof(typename)" .

Feb 15 '06 #10

P: n/a
"Old Wolf" <ol*****@inspire.net.nz> writes:
Keith Thompson wrote:

struct vector *ptr;
ptr = malloc(sizeof struct vector);

is perfectly legal.


sizeof(struct vector)

When you are in the habit of omitting the brackets for "sizeof var",
it's easy to forget to include them for "sizeof(typename)" .


Absolutely correct; thanks for catching my error.

--
Keith Thompson (The_Other_Keith) ks***@mib.org <http://www.ghoti.net/~kst>
San Diego Supercomputer Center <*> <http://users.sdsc.edu/~kst>
We must do something. This is something. Therefore, we must do this.
Feb 15 '06 #11

P: n/a
Keith Thompson wrote:
"Old Wolf" <ol*****@inspire.net.nz> writes:
Keith Thompson wrote:
struct vector *ptr;
ptr = malloc(sizeof struct vector);

is perfectly legal.


sizeof(struct vector)

When you are in the habit of omitting the brackets for "sizeof var",
it's easy to forget to include them for "sizeof(typename)" .

Absolutely correct; thanks for catching my error.

Also it is interesting to name things consistently. At my house..

parens (parentheses) look like ()
brackets look like [] and
braces look like {}

I use space in 'sizeof (typename)' so that sizeof operator cannot so
easily be confused with a function.

--
Joe Wright
"Everything should be made as simple as possible, but not simpler."
--- Albert Einstein ---
Feb 15 '06 #12

P: n/a
Joe Wright <jo********@comcast.net> writes:
Keith Thompson wrote:
"Old Wolf" <ol*****@inspire.net.nz> writes:
Keith Thompson wrote:

struct vector *ptr;
ptr = malloc(sizeof struct vector);

is perfectly legal.

sizeof(struct vector)

When you are in the habit of omitting the brackets for "sizeof var",
it's easy to forget to include them for "sizeof(typename)" . Absolutely correct; thanks for catching my error.

Also it is interesting to name things consistently. At my house..

parens (parentheses) look like ()
brackets look like [] and
braces look like {}


That's what I call them too, but I think common usage differs outside
the US. When it doubt, show the actual characters.
I use space in 'sizeof (typename)' so that sizeof operator cannot so
easily be confused with a function.


That's a good idea.

--
Keith Thompson (The_Other_Keith) ks***@mib.org <http://www.ghoti.net/~kst>
San Diego Supercomputer Center <*> <http://users.sdsc.edu/~kst>
We must do something. This is something. Therefore, we must do this.
Feb 16 '06 #13

P: n/a
Chris Torek wrote:
#include <stdlib.h>
struct vector {
size_t n; /* number of values in the vector */
double val[1]; /* actually size n */
};

struct vector *vec_new(size_t n) {
struct vector *p;

/* need n-1 here because the array has an "extra" element */
p = malloc(sizeof *p + (n ? n - 1 : 0) * sizeof p->val[0]);


I do this as follows:

p = (struct vector *) malloc (offsetof (struct vector, val) +
n * sizeof (p->val[0]));

Which means I need a #include <stddef.h>

--
Paul Hsieh
http://www.pobox.com/~qed/
http://bstring.sf.net/

Feb 16 '06 #14

P: n/a
we******@gmail.com writes:
Chris Torek wrote:
#include <stdlib.h>
struct vector {
size_t n; /* number of values in the vector */
double val[1]; /* actually size n */
};

struct vector *vec_new(size_t n) {
struct vector *p;

/* need n-1 here because the array has an "extra" element */
p = malloc(sizeof *p + (n ? n - 1 : 0) * sizeof p->val[0]);


I do this as follows:

p = (struct vector *) malloc (offsetof (struct vector, val) +
n * sizeof (p->val[0]));

Which means I need a #include <stddef.h>


Why do you uselessly cast the result of malloc()?

--
Keith Thompson (The_Other_Keith) ks***@mib.org <http://www.ghoti.net/~kst>
San Diego Supercomputer Center <*> <http://users.sdsc.edu/~kst>
We must do something. This is something. Therefore, we must do this.
Feb 16 '06 #15

P: n/a
Keith Thompson wrote:
we******@gmail.com writes:
Chris Torek wrote:
#include <stdlib.h>
struct vector {
size_t n; /* number of values in the vector */
double val[1]; /* actually size n */
};

struct vector *vec_new(size_t n) {
struct vector *p;

/* need n-1 here because the array has an "extra" element */
p = malloc(sizeof *p + (n ? n - 1 : 0) * sizeof p->val[0]);


I do this as follows:

p = (struct vector *) malloc (offsetof (struct vector, val) +
n * sizeof (p->val[0]));

Which means I need a #include <stddef.h>


Why do you uselessly cast the result of malloc()?


Because I like to use automatic error detection tools when I program,
and that includes using a C++ compiler (which has stricter type
checking). It means I have to conceed to things like the above in
order to gain the benefit of stronger type checking elsewhere in my
programs.

So if you take the opposite point of view, would it be fair to say that
you avoid things like Lint and ignore warning messages coming from your
compiler? Or are you saying that anything superfluous, like comments
and whitespace, should be eradicated from code?

--
Paul Hsieh
http://www.pobox.com/~qed/
http://bstring.sf.net/

Feb 16 '06 #16

P: n/a
we******@gmail.com writes:
Keith Thompson wrote:

[...]
Why do you uselessly cast the result of malloc()?


Because I like to use automatic error detection tools when I program,
and that includes using a C++ compiler (which has stricter type
checking). It means I have to conceed to things like the above in
order to gain the benefit of stronger type checking elsewhere in my
programs.

So if you take the opposite point of view, would it be fair to say that
you avoid things like Lint and ignore warning messages coming from your
compiler? Or are you saying that anything superfluous, like comments
and whitespace, should be eradicated from code?


Yes, absolutely, because you're always right and anyone who disagrees
with you must be a complete idiot. Thank you for bringing this to my
attention.

--
Keith Thompson (The_Other_Keith) ks***@mib.org <http://www.ghoti.net/~kst>
San Diego Supercomputer Center <*> <http://users.sdsc.edu/~kst>
We must do something. This is something. Therefore, we must do this.
Feb 16 '06 #17

P: n/a
Keith Thompson wrote:
we******@gmail.com writes:
Keith Thompson wrote:

[...]
Why do you uselessly cast the result of malloc()?


Because I like to use automatic error detection tools when I program,
and that includes using a C++ compiler (which has stricter type
checking). It means I have to conceed to things like the above in
order to gain the benefit of stronger type checking elsewhere in my
programs.

So if you take the opposite point of view, would it be fair to say that
you avoid things like Lint and ignore warning messages coming from your
compiler? Or are you saying that anything superfluous, like comments
and whitespace, should be eradicated from code?


Yes, absolutely, because you're always right and anyone who disagrees
with you must be a complete idiot. Thank you for bringing this to my
attention.


You intentionally instigated this. You used the language "uselessly"
and your canned argument against this cast is well known to me, and you
know that. So you respond with this question with the intention of
laying this pathetic trap. But, if you've ever bothered to read my
posts, you've known my counter argument before this post.

My reducto ad absurdum in the second paragraph is meant to address the
position "if you take the opposite point of view". If you understood
what I was saying rather than taking an antagonistic-only stance you
would have realized that what I was saying is that the problem is that
you have not clarified the basis for your point of view, and unless its
insane the ball is in your court to explain why you want to remove this
"useless" cast.

--
Paul Hsieh
http://www.pobox.com/~qed/
http://bstring.sf.net/

Feb 17 '06 #18

P: n/a
we******@gmail.com wrote:
Keith Thompson wrote:
we******@gmail.com writes:
p = (struct vector *) malloc (offsetof (struct vector, val) +
n * sizeof (p->val[0]));

Which means I need a #include <stddef.h>


Why do you uselessly cast the result of malloc()?


Because I like to use automatic error detection tools when I program,
and that includes using a C++ compiler (which has stricter type
checking). It means I have to conceed to things like the above in
order to gain the benefit of stronger type checking elsewhere in my
programs.


So in order to gain a few dubious warnings which quite possibly won't
even apply to the language you're writing in, you prevent your real
compiler from spotting what would be a read and potentially dangerous
mistake?

Sheer brilliance.

Richard
Feb 17 '06 #19

P: n/a
Richard Bos wrote:
we******@gmail.com wrote:
Keith Thompson wrote:
we******@gmail.com writes:
> p = (struct vector *) malloc (offsetof (struct vector, val) +
> n * sizeof (p->val[0]));
>
> Which means I need a #include <stddef.h>

Why do you uselessly cast the result of malloc()?


Because I like to use automatic error detection tools when I program,
and that includes using a C++ compiler (which has stricter type
checking). It means I have to conceed to things like the above in
order to gain the benefit of stronger type checking elsewhere in my
programs.


So in order to gain a few dubious warnings which quite possibly won't
even apply to the language you're writing in, you prevent your real
compiler from spotting what would be a read and potentially dangerous
mistake?


C++ compilers also enforce prototype predeclarations (so would C99
compilers if they existed). So exactly which dangerous mistake do you
think I am going to miss?

--
Paul Hsieh
http://www.pobox.com/~qed/
http://bstring.sf.net/

Feb 17 '06 #20

P: n/a
On 17 Feb 2006 11:02:13 -0800, we******@gmail.com wrote:
Richard Bos wrote: (casting return of malloc) C++ compilers also enforce prototype predeclarations (so would C99
compilers if they existed). So exactly which dangerous mistake do you
think I am going to miss?


C++ does require prototypes (though not using that term).

C99 requires _declarations_ but not necessarily prototypes. That is
enough for the return type, as here, but not arguments.

- David.Thompson1 at worldnet.att.net
Feb 27 '06 #21

This discussion thread is closed

Replies have been disabled for this discussion.