Flexible array members in C99

In a struct with more than one member, the last member of the struct can have incomplete array type. Such a member is called a flexible array member of the struct.

Note

When a struct has a flexible array member, the entire struct itself has incomplete type.

Flexible array members enable you to mimic dynamic type specification in C in the sense that you can defer the specification of the array size to runtime. For example:

 

extern const int n;
typedef struct
{
    int len;
    char p[];
} str;
void foo(void) { size_t str_size = sizeof(str); // equivalent to offsetof(str, p) str *s = malloc(str_size + (sizeof(char) * n)); }

Arrays of Length Zero

http://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html

Zero-length arrays are allowed in GNU C.
They are very useful as the last element of a structure that is really a header for a variable-length object

struct line {
   int length;
   char contents[0];
 };
 
int this_length = 10;

struct line *thisline = (struct line *)malloc (sizeof (struct line) + this_length); 
thisline->length = this_length;

In ISO C90, you would have to give contents a length of 1, which means either you waste space or complicate the argument to malloc.

In ISO C99, you would use a flexible array member, which is slightly different in syntax and semantics:

  • Flexible array members are written as contents[] without the 0.
  • Flexible array members have incomplete type, and so the sizeof operator may not be applied.
    As a quirk of the original implementation of zero-length arrays, sizeof evaluates to zero.
  • Flexible array members 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.
    (However, these uses are permitted by GCC as extensions.)

GCC versions before 3.0 allowed zero-length arrays to be statically initialized, as if they were flexible arrays.
In addition to those cases that were useful, it also allowed initializations in situations that would corrupt later data.
Non-empty initialization of zero-length arrays is now 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.

Instead 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 } };

The convenience of this extension is that f1 has the desired type, eliminating the need to consistently refer to f2.f1.

This has symmetry with normal static arrays, in that an array of unknown size is also written with [].

Of course, this extension only makes sense if the extra data comes at the end of a top-level object,
as otherwise we would be overwriting data at subsequent offsets.
To avoid undue complication and confusion with initialization of deeply nested arrays,
we simply disallow any non-empty initialization except when the structure is the top-level object. For example:

struct foo { int x; int y[]; };
struct bar { struct foo z; };
     
struct foo a = { 1, { 2, 3, 4 } };        // Valid.
struct bar b = { { 1, { } } };            // Valid.
struct bar c = { { 1, { 2, 3, 4 } } };    // Invalid.
struct foo d[1] = { { 1 { 2, 3, 4 } } };  // Invalid.

Arrays of Variable Length

http://gcc.gnu.org/onlinedocs/gcc/Variable-Length.html

Variable-length automatic arrays are allowed in ISO C99, and as an extension GCC accepts them in C90 mode and in C++.
These arrays are declared like any other automatic arrays, but with a length that is not a constant expression.
The storage is allocated at the point of declaration and deallocated when the block scope containing the declaration exits. For example:

     FILE *
     concat_fopen (char *s1, char *s2, char *mode)
     {
       char str[strlen (s1) + strlen (s2) + 1];
       strcpy (str, s1);
       strcat (str, s2);
       return fopen (str, mode);
     }

Jumping or breaking out of the scope of the array name deallocates the storage.
Jumping into the scope is not allowed; you get an error message for it.

You can use the function alloca to get an effect much like variable-length arrays.
The function alloca is available in many other C implementations (but not in all).
On the other hand, variable-length arrays are more elegant.

There are other differences between these two methods.
Space allocated with alloca exists until the containing function returns.
The space for a variable-length array is deallocated as soon as the array name's scope ends.
(If you use both variable-length arrays andalloca in the same function,
deallocation of a variable-length array also deallocates anything more recently allocated with alloca.)

     struct entry
     tester (int len, char data[len][len])
     {
       /* ... */
     }

The length of an array is computed once when the storage is allocated and
is remembered for the scope of the array in case you access it with sizeof.

If you want to pass the array first and the length afterward,
you can use a forward declaration in the parameter list—another GNU extension.

You can also use variable-length arrays as arguments to functions:

     struct entry
     tester (int len; char data[len][len], int len)
     {
       /* ... */
     }

The ‘int len’ before the semicolon is a parameter forward declaration,
and it serves the purpose of making the name len known when the declaration of data is parsed.

You can write any number of such parameter forward declarations in the parameter list.
They can be separated by commas or semicolons, but the last one must end with a semicolon,
which is followed by the “real” parameter declarations.
Each forward declaration must match a “real” declaration in parameter name and data type.
ISO C99 does not support parameter forward declarations.

http://www.gidforums.com/t-15161.html

Variable array inside struct

Are you asking about a struct with a "flexible array member" that is part of the C99 standard?

From ISO/IEC 9899:1999, Section 6.7.2.1, paragraph 16:
"As a special case, 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."


Such a thing might be used as follows:

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

/* a struct with a flexible array member
 *
 * Supported by GNU gcc
 *
 * Compile with: gcc test_flex.c -Wall -W -pedantic -std=c99
 */

typedef struct s_var_ {
    int size;
    double p[]; /* Incomplete array type */
} s_var;

int main()
{
    int i;

    /* suppose we want an array of 8 doubles in the struct: */

    int siz = 8;
    s_var *ps;

    printf("sizeof(int)   = %d, sizeof(double) = %d\n", sizeof(int), sizeof(double));
    printf("sizeof(s_var) = %d\n\n", sizeof(s_var));

    ps = malloc(sizeof(s_var) + siz * sizeof(double));
    ps->size = siz;

    for (i = 0; i < siz; i++) { /* just put something in the array */
        ps->p[i] = 1.5 * i;
    }

    for (i = 0; i < ps->size; i++) {
        printf("ps->p[%d] = %9f\n", i, ps->p[i]);
    }
    printf("\n&(ps->size) = %p\n", &(ps->size));
    for (i = 0; i < ps->size; i++) {
        printf("&(ps->p[%d]) = %p\n", i, &(ps->p[i]));
    }

    free(ps);

    return 0;
}
sizeof(int)   = 4, sizeof(double) = 8
sizeof(s_var) = 4 // Note: the sizeof the struct is known at compile time, and does not include the size of the flexible array.

ps->p[0] =  0.000000
ps->p[1] =  1.500000
ps->p[2] =  3.000000
ps->p[3] =  4.500000
ps->p[4] =  6.000000
ps->p[5] =  7.500000
ps->p[6] =  9.000000
ps->p[7] = 10.500000

&(ps->size) = 0x9d58008
&(ps->p[0]) = 0x9d5800c
&(ps->p[1]) = 0x9d58014
&(ps->p[2]) = 0x9d5801c
&(ps->p[3]) = 0x9d58024
&(ps->p[4]) = 0x9d5802c
&(ps->p[5]) = 0x9d58034
&(ps->p[6]) = 0x9d5803c
&(ps->p[7]) = 0x9d58044

The members of the flexible array are put into contiguous addresses at the end of the other elements of the struct.
As you can see, you can use %p to print the address of ps->size, ps->p[0], ps->p[1], ... 

The struct elements occupy sequential memory locations.

 

Questions & Answers: Creating Variable-size structs

http://www.drdobbs.com/questions-answers-creating-variable-siz/184403480

struct datapacket
{
    int size;
    int data[1];
};

void f()
{
  struct datapacket *pkt = malloc( sizeof(struct datapacket) + sizeof(int)*9 );
// After the call to malloc, pkt points to a struct that has room for the integer field size and an array of 10 ints
// It declares an array of one int, and we added space for nine more
/* further code . . . */ }

You access these fields in the obvious way: pkt->size, pkt->data[3], and so on.

The theoretical problem with this approach is that it uses array indices that are too far past the end of the array. In both C and C++ you are allowed to use an index that points into an array or one past the end of the array. Any other index value results in undefined behavior. So it's valid to use pkt->data[0], and it's valid to talk about the address of pkt->data[1], but index values beyond 1 aren't required to work sensibly. The practical problem with this approach is that you might run into a compiler that checks array bounds, and it would complain about using indices that are beyond those allowed by the language definition.

There's a variation on this old trick that gets around the problem with array bounds checking. Instead of declaring the array to have one element, declare it to have a larger number of elements than you would ever actually use. As with the other version, allocate what you actually need:

struct datapacket
{
    int size;
    int data[1000];
};

void f()
{
  struct datapacket *pkt = malloc( sizeof(struct datapacket) - sizeof(int)*1000 + sizeof(int)*10 );
  /* further code . . . */
}

Once again you've allocated space for the integer field size and an array of 10 ints, and once again you've lied to the compiler, so you can't be sure that this will work. However, just like the previous version, I don't know of a compiler that doesn't handle this the way you'd expect.

Ultimately, though, this is a common enough problem that the language definition ought to provide a solution. The new trick that's allowed under the current proposal has actually been around for several years and will now receive official blessing. Syntactically it looks a lot like the previous examples: you declare an array at the end of your struct. The difference is that you don't specify the size of the array:

struct datapacket
{
    int size;
    int data[];
};

void f()
{
  struct datapacket *pkt = malloc( sizeof(struct datapacket) + sizeof(int)*10 );
  /* further code . . . */
}

This, of course, is invalid in Standard C as it exists today — you cannot have an array of unspecified size as a member of a struct. That's a big advantage over the old trick, because the old trick used a valid construct in a way that meant something different from its obvious meaning. The new trick doesn't have this problem. You don't need to worry that some maintenance programmer will think that this array really has only one element in it. 

 

 

#include <stdio.h>

struct matrix {
  int rows;
  int cols;
  int **val;
};

struct matrix a =
{
  .rows=3,
  .cols=1,
  .val = (int*[3])
  {
    (int[1]){1},
    (int[1]){2},
    (int[1]){3}
  }
};

struct matrix b =
{
  .rows=3,
  .cols=4,
  .val = (int*[3])
  {
    (int[4]){1, 2, 3, 4},
    (int[4]){5, 6, 7, 8},
    (int[4]){9,10,11,12}
  }
};

void print_matrix( char *name, struct matrix *m )
{
  for ( int row = 0; row < m->rows; row++ )
    for ( int col = 0; col < m->cols; col++ )
      printf( "%s[%i][%i]: %i\n", name, row, col, m->val[ row ][ col ] );
  puts( "" );
}

void main( )
{
  print_matrix( "a", &a );
  print_matrix( "b", &b );
}

 

posted @ 2013-05-25 02:20  IAmAProgrammer  阅读(2510)  评论(0编辑  收藏  举报