Unsized array declaration inside struct ok? [duplicate]
Asked Answered
B

2

5

Is the following supposed to be valid for the declaration of table_type, specifically the e[]:

struct table_type
{
   unsigned int8 a;
   unsigned int8 b;
   unsigned int8 c;
   unsigned int8 d;
   unsigned int8 e[];
};

struct table_type table[] =
{
{  0,   1,  2,  3, {  4,  5,  6,  7,  8} },
{  9,  10, 11, 12, { 13, 14, 15, 16, 17} },
{ 18,  19, 20, 21, { 22, 23, 24, 25, 26} },
{ 27,  28, 29, 30, { 31, 32, 33, 34, 35} },
{ 36,  37, 38, 39, { 40, 41, 42, 43, 44} },
{ 45,  46, 47, 48, { 49, 50, 51, 52, 53} }
};

void main()
{
   unsigned int8 i = 0;
   unsigned int8 j = 0;

   for( i=0; i<6; i++ )
   {
      printf("\n");
      for( j=0; j<=4; j++ )
         printf( "i=%u j=%u k=%u\n", i, j, table[i].e[j] );
   }
}

All this does is print out the e elements of each line in table. Here is the output, which is obviously goofy:

i=0 j=0 k=4
i=0 j=1 k=9
i=0 j=2 k=10
i=0 j=3 k=11
i=0 j=4 k=12

i=1 j=0 k=13
i=1 j=1 k=18
i=1 j=2 k=19
i=1 j=3 k=20
i=1 j=4 k=21

i=2 j=0 k=22
i=2 j=1 k=27
i=2 j=2 k=28
i=2 j=3 k=29
i=2 j=4 k=30

i=3 j=0 k=31
i=3 j=1 k=36
i=3 j=2 k=37
i=3 j=3 k=38
i=3 j=4 k=39

i=4 j=0 k=40
i=4 j=1 k=45
i=4 j=2 k=46
i=4 j=3 k=47
i=4 j=4 k=48

i=5 j=0 k=49
i=5 j=1 k=50
i=5 j=2 k=51
i=5 j=3 k=52
i=5 j=4 k=53

Notice that it is correct in the last block when i=5. When I replace e[] with e[5], the output is all correct. I am using the CCS C Compiler and a Microchip microcontroller. I was just curious if this is a bug or what.

Behistun answered 8/7, 2014 at 19:21 Comment(4)
This is a flexible array member in C99. Here is some more info: d3s.mff.cuni.cz/~holub/c_features.html. Under "Flexible Array as a Member of a Structure".Softa
@embedded_guy: Close, but not quite. This question is about the flexible array member, which is a distinct feature from variable length arrays.Clerissa
@Clifford: Because I thought this question was a duplicate, and the original question has a good answer. However, as Jonathan Leffler pointed out, it's not exactly a dupe since this example does have UB in it (which I incidentally didn't notice).Clerissa
@AdamRosenfield as far as I can tell there is no standard way to do what the OP wants without using non-portable gnu extension. So although there is UB I think the correct answer comes down to the solution you indicated in the possible duplicate. I don't see any other portable solutions.Wanderjahr
H
8

You're using undefined behaviour and probably running into a compiler bug at the same time. Note that GCC 4.9.0 (compiled on an Ubuntu 12.04 derivative but running on an Ubuntu 14.04 derivative) gives lots of errors for this trivial adaptation of your code:

#include <stdio.h>
#include <stdint.h>

struct table_type
{
   uint8_t a;
   uint8_t b;
   uint8_t c;
   uint8_t d;
   uint8_t e[];
};

struct table_type table[] =
{
  {  0,   1,  2,  3, {  4,  5,  6,  7,  8} },
  {  9,  10, 11, 12, { 13, 14, 15, 16, 17} },
  { 18,  19, 20, 21, { 22, 23, 24, 25, 26} },
  { 27,  28, 29, 30, { 31, 32, 33, 34, 35} },
  { 36,  37, 38, 39, { 40, 41, 42, 43, 44} },
  { 45,  46, 47, 48, { 49, 50, 51, 52, 53} }
};

int main(void)
{
   uint8_t i = 0;
   uint8_t j = 0;

   for( i=0; i<6; i++ )
   {
      printf("\n");
      for( j=0; j<5; j++ )
         printf( "i=%u j=%u k=%u\n", i, j, table[i].e[j] );
   }
}

Compilation errors:

$ gcc -g -O3 -std=c99 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes -Wold-style-definition -Wold-style-declaration -Werror  -c vla.c
vla.c:15:3: error: initialization of flexible array member in a nested context
   {  0,   1,  2,  3, {  4,  5,  6,  7,  8} },
   ^
vla.c:15:3: error: (near initialization for ‘table[0].e’)
vla.c:16:3: error: initialization of flexible array member in a nested context
   {  9,  10, 11, 12, { 13, 14, 15, 16, 17} },
   ^
vla.c:16:3: error: (near initialization for ‘table[1].e’)
vla.c:17:3: error: initialization of flexible array member in a nested context
   { 18,  19, 20, 21, { 22, 23, 24, 25, 26} },
   ^
vla.c:17:3: error: (near initialization for ‘table[2].e’)
vla.c:18:3: error: initialization of flexible array member in a nested context
   { 27,  28, 29, 30, { 31, 32, 33, 34, 35} },
   ^
vla.c:18:3: error: (near initialization for ‘table[3].e’)
vla.c:19:3: error: initialization of flexible array member in a nested context
   { 36,  37, 38, 39, { 40, 41, 42, 43, 44} },
   ^
vla.c:19:3: error: (near initialization for ‘table[4].e’)
vla.c:20:3: error: initialization of flexible array member in a nested context
   { 45,  46, 47, 48, { 49, 50, 51, 52, 53} }
   ^
vla.c:20:3: error: (near initialization for ‘table[5].e’)

The fact that you are not getting similar errors suggests that your compiler is rather old, or otherwise not as helpful as it could be. Note that even though I have stream of stringent compiler warning options enabled, the compilation also fails with the same messages with just gcc -c vla.c (still an error, unconditionally).

You can't have an array of structures with flexible array members; the initialization ought not to be allowed. You can have arrays of pointers to structures containing FAMs, but not arrays of FAMs.

Using a GCC extension

Note that this compiles without warnings (until you add -pedantic to the compiler options I use):

struct table_type t0 =
  {  0,   1,  2,  3, {  4,  5,  6,  7,  8} };

This leads to this code which works on the system I'm using (but it is a solution using GCC extension to standard C, as pointed out by Shafik Yaghmour in a comment):

#include <stdio.h>
#include <stdint.h>

struct table_type
{
   uint8_t a;
   uint8_t b;
   uint8_t c;
   uint8_t d;
   uint8_t e[];
};

struct table_type t0 =
  {  0,   1,  2,  3, {  4,  5,  6,  7,  8} };
struct table_type t1 =
  {  9,  10, 11, 12, { 13, 14, 15, 16, 17} };
struct table_type t2 =
  { 18,  19, 20, 21, { 22, 23, 24, 25, 26} };
struct table_type t3 =
  { 27,  28, 29, 30, { 31, 32, 33, 34, 35} };
struct table_type t4 =
  { 36,  37, 38, 39, { 40, 41, 42, 43, 44} };
struct table_type t5 =
  { 45,  46, 47, 48, { 49, 50, 51, 52, 53} };

struct table_type *pointers[] = { &t0, &t1, &t2, &t3, &t4, &t5 };

int main(void)
{
   uint8_t i = 0;
   uint8_t j = 0;

   for( i=0; i<6; i++ )
   {
      printf("\n");
      for( j=0; j<5; j++ )
         printf( "i=%u j=%u k=%u\n", i, j, pointers[i]->e[j] );
   }
}

Sample output:

i=0 j=0 k=4
i=0 j=1 k=5
i=0 j=2 k=6
i=0 j=3 k=7
i=0 j=4 k=8

i=1 j=0 k=13
i=1 j=1 k=14
i=1 j=2 k=15
i=1 j=3 k=16
i=1 j=4 k=17

i=2 j=0 k=22
i=2 j=1 k=23
i=2 j=2 k=24
i=2 j=3 k=25
i=2 j=4 k=26

i=3 j=0 k=31
i=3 j=1 k=32
i=3 j=2 k=33
i=3 j=3 k=34
i=3 j=4 k=35

i=4 j=0 k=40
i=4 j=1 k=41
i=4 j=2 k=42
i=4 j=3 k=43
i=4 j=4 k=44

i=5 j=0 k=49
i=5 j=1 k=50
i=5 j=2 k=51
i=5 j=3 k=52
i=5 j=4 k=53

(Incidentally, void main() is unorthodox C except in Microsoft-land; however, you imply that you're working in an embedded system and maybe that has special rules. I replaced void main() { ... } with the standard int main(void) { ... } notation. The use of unsigned int8 is also non-standard because int8 is not standard, but probably comes from being an embedded system. I replaced unsigned int8 with uint8_t from <stdint.h>)

Avoiding the GCC extension

In this example, all the arrays are the same size, so there really is no merit in using the flexible array member notation. The simplest solution to avoiding the problems of GCC extensions, therefore, is to provide the correct size for the array:

#include <stdio.h>
#include <stdint.h>

struct table_type
{
    uint8_t a;
    uint8_t b;
    uint8_t c;
    uint8_t d;
    uint8_t e[5];
};

struct table_type table[] =
{
    {  0,   1,  2,  3, {  4,  5,  6,  7,  8} },
    {  9,  10, 11, 12, { 13, 14, 15, 16, 17} },
    { 18,  19, 20, 21, { 22, 23, 24, 25, 26} },
    { 27,  28, 29, 30, { 31, 32, 33, 34, 35} },
    { 36,  37, 38, 39, { 40, 41, 42, 43, 44} },
    { 45,  46, 47, 48, { 49, 50, 51, 52, 53} },
};

int main(void)
{
    uint8_t i = 0;
    uint8_t j = 0;

    for (i = 0; i < 6; i++)
    {
        printf("\n");
        for (j = 0; j < 5; j++)
            printf("i=%u j=%u k=%u\n", i, j, table[i].e[j]);
    }
}

Assuming that the flexible array members do in fact need to be different sizes, then you have to use dynamic memory allocation and arrays of pointers to structures containing FAMs.

#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

struct table_type
{
    uint8_t a;
    uint8_t b;
    uint8_t c;
    uint8_t d;
    uint8_t len;
    uint8_t e[];
};

struct table_type *pointers[6];

struct table_info
{
    uint8_t a;
    uint8_t b;
    uint8_t c;
    uint8_t d;
    uint8_t num;
    uint8_t rep;
    uint8_t info[6];
};

struct table_info data[] =
{
    {  0,   1,  2,  3,  5, 1, {  4,  5,  6,  7,  8,  0, } },
    {  9,  10, 11, 12,  4, 2, { 13, 14, 15, 16,  0,  0, } },
    { 18,  19, 20, 21,  3, 3, { 22, 23, 24,  0,  0,  0, } },
    { 27,  28, 29, 30,  4, 3, { 31, 32, 33, 34,  0,  0, } },
    { 36,  37, 38, 39,  5, 2, { 40, 41, 42, 43, 44,  0, } },
    { 45,  46, 47, 48,  6, 2, { 49, 50, 51, 52, 53, 79, } },
};

int main(void)
{
    for (uint8_t i = 0; i < 6; i++)
    {
        assert(data[i].num * data[i].rep < UINT8_MAX);
        size_t nelem = data[i].num * data[i].rep;
        size_t bytes = sizeof(struct table_type) + nelem * sizeof(pointers[i]->e[0]);
        pointers[i] = malloc(bytes);
        pointers[i]->a = data[i].a;
        pointers[i]->b = data[i].b;
        pointers[i]->c = data[i].c;
        pointers[i]->d = data[i].d;
        pointers[i]->len = data[i].num * data[i].rep;

        uint8_t n = 0;
        for (uint8_t j = 0; j < data[i].rep; j++)
        {
            for (uint8_t k = 0; k < data[i].num; k++)
                pointers[i]->e[n++] = data[i].info[k];
        }
    }

    for (uint8_t i = 0; i < 6; i++)
    {
        printf("index = %2d, a = %2d, b = %2d, c = %2d, d = %2d, len = %2d\n",
               i, pointers[i]->a, pointers[i]->b, pointers[i]->c,
               pointers[i]->d, pointers[i]->len);
        const char *pad = "        ";
        for (uint8_t j = 0; j < pointers[i]->len; j++)
        {
            printf("%s%2d", pad, pointers[i]->e[j]);
            pad = ", ";
        }
        putchar('\n');
    }
}

Example output:

index =  0, a =  0, b =  1, c =  2, d =  3, len =  5
         4,  5,  6,  7,  8
index =  1, a =  9, b = 10, c = 11, d = 12, len =  8
        13, 14, 15, 16, 13, 14, 15, 16
index =  2, a = 18, b = 19, c = 20, d = 21, len =  9
        22, 23, 24, 22, 23, 24, 22, 23, 24
index =  3, a = 27, b = 28, c = 29, d = 30, len = 12
        31, 32, 33, 34, 31, 32, 33, 34, 31, 32, 33, 34
index =  4, a = 36, b = 37, c = 38, d = 39, len = 10
        40, 41, 42, 43, 44, 40, 41, 42, 43, 44
index =  5, a = 45, b = 46, c = 47, d = 48, len = 12
        49, 50, 51, 52, 53, 79, 49, 50, 51, 52, 53, 79

This is merely one way of demonstrating different size flexible array member arrays, and of initializing them. More typically, you'd collect the size and initialization data from some external device — a file on disk or an I/O channel of some sort.

Hungary answered 8/7, 2014 at 20:11 Comment(5)
Ok, I understand Jonathan, it doesn't know how much space to allocate for e. I will send a bug report to CCS...Maybe int main() would work, but there is nothing to return a value to, so it doesn't really make sense.Behistun
The int main(void) change was mainly necessary to get past the compiler options I use on a regular hosted system. For you in your embedded environment (freestanding environment in the standard terminology), void main() may well be OK. Do, please, understand that I'm not knocking that; I'm just observing that I made the change and why I made the change.Hungary
No problem. What happened to the other answer? This has been an odd experience posting here. People editing and deleting things.Behistun
One answer was deleted by the person who posted it. It is a collaborative site; people are encouraged to edit questions and answers for material improvements. (Some people edit things with immaterial improvements; that's not so good.)Hungary
@ShafikYaghmour: That is certainly a possibility. Let's see: add -pedantic and you get errors vla23.c:14:3: error: initialization of a flexible array member [-Werror=pedantic] identifying the line { 0, 1, 2, 3, { 4, 5, 6, 7, 8} }; and noting vla23.c:14:3: error: (near initialization for ‘t0.e’) [-Werror=pedantic]. So yes, you're correct. You're not supposed to be able to initialize structures containing an FAM (I should really rename the source file, but then again, it doesn't matter very much). I did vaguely wonder about it; I should have wondered a bit harder. I'll update.Hungary
P
0

It is a C99 flexible array member.

Flexible array members...

  • are written as contents[] without a dimension value,
  • have incomplete type, and so the sizeof operator may not be applied,
  • may only appear as the last member of a struct that is otherwise non-empty.
  • A structure containing a flexible array member, or a union containing such a structure (possibly recursively), may not be a member of a structure or an element of an array.

It allows a structure to be defined for use as a header for a variable-length object.

Pasty answered 8/7, 2014 at 19:43 Comment(9)
It appears I am not violating those rules, but the array e[] is not being evaluated correctly, see my printout. Is this a bug?Behistun
@BasicPoke: Incomplete types cannot be part of an aggregate type (like an array).Indicator
Sorry, I don't know what that means "incomplete types." Are you saying my code is not allowed, mafso?Behistun
@BasicPoke, as mafso points out, you are clearly breaking the last rule.Pasty
@Behistun : Incomplete type - It has insufficient type information to know how big the object is because it is undefined and variable). Also assignment (x = y) of such a structure will not work.Pasty
I don't see how I am breaking the rule. The structure (does it mean struct?) containing the array is not a member of a structure or an element in an array.Behistun
If you say I am breaking the rule, I trust you, I will just move on and put a # between the [].Behistun
@Behistun yes it is; it's an element of the array table.Reasoning
@Behistun : "A structure containing a flexible array member [...] may not be [...] an element of an array." so struct table_type may not be an element of table - clear now?Pasty

© 2022 - 2024 — McMap. All rights reserved.