Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unsized array declaration inside struct ok? [duplicate]

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.

like image 265
BasicPoke Avatar asked Jul 08 '14 19:07

BasicPoke


1 Answers

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.

like image 53
Jonathan Leffler Avatar answered Oct 11 '22 22:10

Jonathan Leffler