Discussion:
sizeof on a flexible array member.
Add Reply
sinbad
2017-07-13 04:49:33 UTC
Reply
Permalink
Raw Message
Hi,

I have a structure with flexible array member. I've initialized the
array and i want to perform the sizeof operator on this, but it results
in to the following error. what's happening here and is there any other
way that i can get the size without looping through the array ?

test.c
-------------------------

#include "stdio.h"

typedef struct test_s {
int x;
int y;
}test_t;

typedef struct abc_s {
int size;
test_t data[];
}abc_t;

abc_t glob = {
.data = {
{
.x = 1,
.y = 2,
},
{
.x = 1,
.y = 2,
},
},
};

int main()
{
glob.size = sizeof(glob.data);

return 0;
}

-----------------------

$ gcc test.c

1.c: In function ‘main’:
1.c:28:23: error: invalid application of ‘sizeof’ to incomplete type ‘struct test_t[]’
glob.size = sizeof(glob.data);
^
Melzzzzz
2017-07-13 04:52:54 UTC
Reply
Permalink
Raw Message
Post by sinbad
Hi,
I have a structure with flexible array member. I've initialized the
array and i want to perform the sizeof operator on this, but it results
in to the following error. what's happening here and is there any other
way that i can get the size without looping through the array ?
Remember size in another member. sizeof is compile time operator (Don't
know how it work on stack dynamic arrays, though)
--
press any key to continue or any other to quit...
Andrey Tarasevich
2017-07-13 06:02:09 UTC
Reply
Permalink
Raw Message
Post by sinbad
...
typedef struct abc_s {
int size;
test_t data[];
}abc_t;
abc_t glob = {
.data = {
{
.x = 1,
.y = 2,
},
{
.x = 1,
.y = 2,
},
},
};
This is not allowed in C language. You cannot "define space" for a
flexible array member in a static or local definition of a struct object
with such a member. Flexible array members do not accept initializers.

If you define a local or static object with flexible array member, the
memory for the flexible array member will not be allocated. E.g.

abc_t abc = { 0 };

The flexible array member can be referred to through '.' and '->'
operators, but it will act as an array of size 1 whose only element is
inaccessible.

The only way to allocate actual memory space for flexible array member
is to allocate it in the heap using appropriately sized 'malloc' or
'calloc' call. E.g.

abc_t *p_abc = malloc(sizeof *p_abc + 2 * sizeof *p->abc);

If what you have in your code quoted above compiles, you are dealing
with non-standard behavior, a compiler extension.
Post by sinbad
glob.size = sizeof(glob.data);
Extension or not, there's no point in applying 'sizeof' to flexible
array member. And, as the compiler already told you, its type is
incomplete making this application of 'sizeof' illegal.
--
Best regards,
Andrey Tarasevich
Andrey Tarasevich
2017-07-13 06:03:27 UTC
Reply
Permalink
Raw Message
Post by Andrey Tarasevich
The only way to allocate actual memory space for flexible array member
is to allocate it in the heap using appropriately sized 'malloc' or
'calloc' call. E.g.
abc_t *p_abc = malloc(sizeof *p_abc + 2 * sizeof *p->abc);
A typo. I meant

abc_t *p_abc = malloc(sizeof *p_abc + 2 * sizeof *p_abc->data);
s***@casperkitty.com
2017-07-13 16:06:29 UTC
Reply
Permalink
Raw Message
Post by Andrey Tarasevich
The only way to allocate actual memory space for flexible array member
is to allocate it in the heap using appropriately sized 'malloc' or
'calloc' call. E.g.
Nearly all implementations can be configured so as to work with static
allocations. Given structure types like:

struct point { uint16_t x,y; };
struct polygon { uint32_t count; struct point coords[]; }
struct polygon3 { uint32_t count; struct point coords[3]; }
struct polygon4 { uint32_t count; struct point coords[4]; }
struct polygon5 { uint32_t count; struct point coords[5]; }
struct polygon6 { uint32_t count; struct point coords[6]; }

it's possible to write a function to accept any of those, or any structure
of that general form:

void print_vertices(void *poly)
{
int i;
struct polygon *p = poly;
for (i=0; i < p->count; i++)
printf("(%d,%d)", p->coords[i].x, p->coords[i].y);
printf("\n");
}

struct polygon3 my_triangle =
{ 3, {{1,2}, {3,4}, {5,6}} };
struct polygon5 my_pentagon =
{ 5, {{1,2}, {3,4}, {5,6}, {7,8}, {9,10}} };
void test(void)
{
print_vertices(&my_triangle);
print_vertices(&my_pentagon);
}

Some compilers interpret the Standard's failure to mandate support for that
construct as an indication that it shouldn't be supported by default. Since
Since any decent compiler will be configurable to support such a feature,
however ,I'd say such an approach is far preferable to nonsense like:

struct polygon3 my_triangle_init =
{ 3, {{1,2}, {3,4}, {5,6}} };
struct polygon5 my_pentagon_init =
{ 5, {{1,2}, {3,4}, {5,6}, {7,8}, {9,10}} };
struct polygon *my_triangle, my_pentagon;

int shapes_dejavu = 0;
void init_shapes(void)
{
my_triangle = malloc(sizeof my_triangle_init);
if (!my_triangle) exit(EXIT_FAILURE);
my_pentagon = malloc(sizeof my_pentagon_init);
if (!my_pentagon) exit(EXIT_FAILURE);
memcpy(my_triangle, &my_triangle_init, sizeof my_triangle_init);
memcpy(my_pentagon, &my_pentagon_init, sizeof my_pentagon_init);
shapes_dejavu = 1;
}
#define init_shapes_if_needed() (init_shapes_dejavu || (init_shapes,0))

void test(void)
{
init_shapes_if_needed();
print_vertices(my_triangle);
print_vertices(my_pentagon);
}

Among other things, if compilers gain the small amount of intelligence
needed to support the first construct without having to disable type-based
aliasing optimizations entirely, code written to use the construct will be
able to benefit from that, but code written to work around compilers'
lack of such intelligence will continue to be a mess.
Tim Rentsch
2017-07-14 22:37:20 UTC
Reply
Permalink
Raw Message
Post by sinbad
...
typedef struct abc_s {
int size;
test_t data[];
}abc_t;
abc_t glob = {
.data = {
{
.x = 1,
.y = 2,
},
{
.x = 1,
.y = 2,
},
},
};
This is not allowed in C language. You cannot "define space" for a
flexible array member in a static or local definition of a struct
object with such a member. Flexible array members do not accept
initializers. [...]
True, but essentially the same effect can be achieved using a
union. For this example (disclaimer: not compiled):

union {
struct { int n; test_t t[2]; } fixed;
abc_t flexible;
} glob_union = { .fixed = {
2,
{ {1,2}, {1,2} }
}};

abc_t *glob = & glob_union.flexible;
// or perhaps use a macro: #define GLOB glob_union.flexible

With a bit of effort a macro can be defined to make this easier,
including not having to calculate the array extent or value for
fixed.n (ie, these values are computed as part of the macro
expansion).

It can be argued whether the Standard guarantees this will work.
In practice I expect it will work just fine as long as the offset
of fixed.t equals the offset of flexible.data, which is highly
likely to be true and in any case can be checked with a static
assertion (which could be done as part of the macro definition).
s***@casperkitty.com
2017-07-14 23:36:20 UTC
Reply
Permalink
Raw Message
Post by Tim Rentsch
It can be argued whether the Standard guarantees this will work.
In practice I expect it will work just fine as long as the offset
of fixed.t equals the offset of flexible.data, which is highly
likely to be true and in any case can be checked with a static
assertion (which could be done as part of the macro definition).
Under gcc's interpretation of the Standard, the only way to make such code
have defined behavior would be to have all objects which would need to use
any of those types use a union containing all of them. The problem is not
with offsets, but rather with aliasing. The authors of gcc claim that it's
unclear whether the Common Initial Sequence is applicable to accesses which
are made through non-union pointer types, even though the Standard says that
a declaration of a complete union *type* be visible, and nowhere says that
accesses be performed through lvalues of union type.

Since nothing in the Standard would justify the use of a pointer of union
type to access something that was not, in fact, a union object (unless
one holds that the aliasing rules refer only to the type of an lvalue
expression, rather than its constituent components), gcc's interpretation
of the Standard would require that all objects of the various-sized
aggregate types be objects of the same union type. That would effectively
negate the benefit of having different sizes of arrays in the first place,
since every object would need to reserve enough space for the largest
array.

Noob
2017-07-13 12:21:00 UTC
Reply
Permalink
Raw Message
Post by sinbad
I have a structure with flexible array member. I've initialized the
array and I want to perform the sizeof operator on this, but it results
in to the following error. what's happening here and is there any other
way that i can get the size without looping through the array ?
test.c
-------------------------
#include "stdio.h"
typedef struct test_s {
int x;
int y;
}test_t;
typedef struct abc_s {
int size;
test_t data[];
}abc_t;
abc_t glob = {
.data = {
{
.x = 1,
.y = 2,
},
{
.x = 1,
.y = 2,
},
},
};
int main()
{
glob.size = sizeof(glob.data);
return 0;
}
-----------------------
$ gcc test.c
1.c:28:23: error: invalid application of ‘sizeof’ to incomplete type ‘struct test_t[]’
glob.size = sizeof(glob.data);
^
Ouch... I very much expected gcc to warn about the initializer o_O

$ gcc-6 -std=c99 -Wall -Wextra 1.c
1.c: In function 'main':
1.c:28:23: error: invalid application of 'sizeof' to incomplete type 'test_t[] {aka struct test_s[]}'
glob.size = sizeof(glob.data);
^

Passing -pedantic seems to be required:

$ gcc-6 -std=c99 -Wall -Wextra -pedantic 1.c
1.c:14:13: warning: initialization of a flexible array member [-Wpedantic]
.data = {
^
1.c:14:13: note: (near initialization for 'glob.data')
1.c: In function 'main':
1.c:28:23: error: invalid application of 'sizeof' to incomplete type 'test_t[] {aka struct test_s[]}'
glob.size = sizeof(glob.data);
^

https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html
Post by sinbad
Non-empty initialization of zero-length arrays is treated like any
case where there are more initializer elements than the array holds,
in that a suitable warning about "excess elements in array" is given,
and the excess elements (all of them, in this case) are ignored.
GCC allows static initialization of flexible array members. This is
equivalent to defining a new structure containing the original
structure followed by an array of sufficient size to contain the
data. E.g. in the following, f1 is constructed as if it were declared
like f2.
struct f1 {
int x; int y[];
} f1 = { 1, { 2, 3, 4 } };
struct f2 {
struct f1 f1; int data[3];
} f2 = { { 1 }, { 2, 3, 4 } };
Regards.
Ben Bacarisse
2017-07-13 14:07:00 UTC
Reply
Permalink
Raw Message
Noob <***@127.0.0.1> writes:
<snip>
Post by Noob
Ouch... I very much expected gcc to warn about the initializer o_O
<snip>
Are you new round here? ;-)

<snip>
--
Ben.
Noob
2017-07-13 17:16:17 UTC
Reply
Permalink
Raw Message
Post by Ben Bacarisse
<snip>
Post by Noob
Ouch... I very much expected gcc to warn about the initializer o_O
<snip>
Are you new round here? ;-)
Well, my nym is Noob after all ;-)

I must admit that it's never been crystal-clear to me how
-std and -pedantic interact.

Let's fix that...
Post by Ben Bacarisse
The compiler can accept several base standards, such as 'c90' or
'c++98', and GNU dialects of those standards, such as 'gnu90' or
'gnu++98'. When a base standard is specified, the compiler accepts
all programs following that standard plus those using GNU extensions
that do not contradict it. For example, -std=c90 turns off certain
features of GCC that are incompatible with ISO C90, such as the asm
and typeof keywords, but not other GNU extensions that do not have a
expression. On the other hand, when a GNU dialect of a standard is
specified, all features supported by the compiler are enabled, even
when those features change the meaning of the base standard. As a
result, some strict-conforming programs may be rejected. The
particular standard is used by -Wpedantic to identify which features
are GNU extensions given that version of the standard. For example
-std=gnu90 -Wpedantic warns about C++ style '//' comments, while
-std=gnu99 -Wpedantic does not.
-Wpedantic
Issue all the warnings demanded by strict ISO C and ISO C++; reject
all programs that use forbidden extensions, and some other programs
that do not follow ISO C and ISO C++. For ISO C, follows the version
of the ISO C standard specified by any -std option used.
Valid ISO C and ISO C++ programs should compile properly with or
without this option (though a rare few require -ansi or a -std option
specifying the required version of ISO C). However, without this
option, certain GNU extensions and traditional C and C++ features are
supported as well. With this option, they are rejected.
-Wpedantic does not cause warning messages for use of the alternate
keywords whose names begin and end with '__'. Pedantic warnings are
also disabled in the expression that follows __extension__. However,
only system header files should use these escape routes; application
programs should avoid them. See Alternate Keywords.
Some users try to use -Wpedantic to check programs for strict ISO C
it finds some non-ISO practices, but not all—only those for which ISO
C requires a diagnostic, and some others for which diagnostics have
been added.
A feature to report any failure to conform to ISO C might be useful
in some instances, but would require considerable additional work and
would be quite different from -Wpedantic. We don't have plans to
support such a feature in the near future.
Where the standard specified with -std represents a GNU extended
dialect of C, such as 'gnu90' or 'gnu99', there is a corresponding
base standard, the version of ISO C on which the GNU extended dialect
is based. Warnings from -Wpedantic are given where they are required
by the base standard. (It does not make sense for such warnings to be
given only for features not in the specified GNU C dialect, since by
definition the GNU dialects of C include all features the compiler
supports with the given option, and there would be nothing to warn
about.)
-pedantic-errors
Give an error whenever the base standard (see -Wpedantic) requires a
diagnostic, in some cases where there is undefined behavior at
compile-time and in some other cases that do not prevent compilation
of programs that are valid according to the standard. This is not
equivalent to -Werror=pedantic, since there are errors enabled by
this option and not enabled by the latter and vice versa.
Therefore, the most comprehensive set of flags seems to be:

gcc -std=c99 -Wall -Wpedantic -pedantic-errors

and perhaps a few more from the set of -Wextra

Regards.
David Brown
2017-07-13 20:55:44 UTC
Reply
Permalink
Raw Message
Post by Noob
gcc -std=c99 -Wall -Wpedantic -pedantic-errors
and perhaps a few more from the set of -Wextra
It is certainly not the "most comprehensive" set of flags - there are
many, many more flags than that. But it is close to the best you can
get if you want gcc to treat your code as C99 and give errors in many
cases where it does not match. I usually have -Wextra too, and it is
worth having -O1 because that enables more analysis that leads to better
static error checking as a byproduct of optimisation.

And of course, "-std=c99 -Wpedantic" is only useful if strict standards
checking is of interest to you - for many gcc users, "-std=gnu99" or
"-std=gnu11" is more useful because many gcc extensions are actually
useful in practice.
Kenny McCormack
2017-07-14 13:33:10 UTC
Reply
Permalink
Raw Message
In article <ok8mh6$jks$***@dont-email.me>,
David Brown <***@hesbynett.no> wrote:
...
Post by David Brown
And of course, "-std=c99 -Wpedantic" is only useful if strict standards
checking is of interest to you - for many gcc users, "-std=gnu99" or
"-std=gnu11" is more useful because many gcc extensions are actually
useful in practice.
I think "useful" is another word you cannot say in CLC.

You and Kiki need to get together on this.
--
People sleep peaceably in their beds at night only because rough
men stand ready to do violence on their behalf.

George Orwell
sinbad
2017-07-14 07:08:53 UTC
Reply
Permalink
Raw Message
Post by Noob
Ouch... I very much expected gcc to warn about the initializer o_O
But why though. while the following seems to be perfectly acceptable
as per cstd.

int arr[] = {1,2,3};
Noob
2017-07-14 14:46:10 UTC
Reply
Permalink
Raw Message
Post by sinbad
Post by Noob
Ouch... I very much expected gcc to warn about the initializer o_O
But why though. while the following seems to be perfectly acceptable
as per cstd.
int arr[] = {1,2,3};
Why what? Please be more specific.
(Also which standard are we discussing C89, C99, C11,
something else?)

Regards.
Ben Bacarisse
2017-07-14 19:06:19 UTC
Reply
Permalink
Raw Message
Post by Noob
Post by sinbad
Post by Noob
Ouch... I very much expected gcc to warn about the initializer o_O
But why though. while the following seems to be perfectly acceptable
as per cstd.
int arr[] = {1,2,3};
Why what?
I think sinbad is asking why

int arr[] = { 1, 2, 3 };

is OK but

struct S { int n; int arr[] } = { 99, { 1, 2, 3 } };

is not (in C99 and up). I don't have much to offer in answer which is
why I left the original question alone. It could have been permitted,
but would have led to other anomalies. structs with flexible array
members are almost always dynamically allocated so there would be little
practical value in requiring it.

<snip>
--
Ben.
James R. Kuyper
2017-07-14 19:13:40 UTC
Reply
Permalink
Raw Message
On 07/14/2017 03:06 PM, Ben Bacarisse wrote:
...
Post by Ben Bacarisse
I think sinbad is asking why
int arr[] = { 1, 2, 3 };
is OK but
struct S { int n; int arr[] } = { 99, { 1, 2, 3 } };
is not (in C99 and up). I don't have much to offer in answer which is
why I left the original question alone. It could have been permitted,
but would have led to other anomalies. structs with flexible array
members are almost always dynamically allocated so there would be little
practical value in requiring it.
They're dynamically allocated because that's pretty much the only way
that works under the current standard (I think putting them in a union
with a larger object should also work, but I've never tried it). Such a
change would make it possible to for them to occur in objects with
static or automatic storage duraction, and if it had been allowed, I
think there'd be a fair number of cases where people would want to do it.
s***@casperkitty.com
2017-07-14 19:40:34 UTC
Reply
Permalink
Raw Message
Post by James R. Kuyper
They're dynamically allocated because that's pretty much the only way
that works under the current standard (I think putting them in a union
with a larger object should also work, but I've never tried it). Such a
change would make it possible to for them to occur in objects with
static or automatic storage duraction, and if it had been allowed, I
think there'd be a fair number of cases where people would want to do it.
There are many cases where people not only want to do such things, but do
so using a technique which quality implementations will support except
on obscure platforms. The Standard makes allowances for obscure platforms
where structures like, e.g.

struct thing3 { int id; short ct; short dat3[3]; };
struct thing4 { int id; short ct; short dat4[4]; };

might be laid out with dat3 at offset 6 and dat4 at offset 8, but most
implementations will lay out dat3 and dat4 at the same offset, and
quality implementations will allow programmers to exploit that.
s***@casperkitty.com
2017-07-14 19:20:16 UTC
Reply
Permalink
Raw Message
Post by Ben Bacarisse
is not (in C99 and up). I don't have much to offer in answer which is
why I left the original question alone. It could have been permitted,
but would have led to other anomalies. structs with flexible array
members are almost always dynamically allocated so there would be little
practical value in requiring it.
The way gcc implements the extension, the storage following the structure
will contain the values specified for the array, but applying sizeof to
a structure initialized in such fashion will yield the size of the structure
rather than the amount of static/automatic storage allocated to it (which
would include the array contents).
Loading...