4

I try to define a struct type with array members of variable size like this:

typedef struct {
  const int M;
  const int N;
  int       x[];    // Should be 1-D M elements
  int       y[][];  // 2-D M*N elements
} xy_t;

The reason for variable sized arrays is that I have a function that should work on variable dimensions.
However that gives an error, so I rewrote to:

typedef struct {
  const int M;
  const int N;
  int       *x;    // 1-D M elements
  int       **y;   // 2-D M* elements
} xy_t;

which compile fine. However, the problem is how to I initialize this?

static xy_t xy = {.M = 3, .N = 2, .x = ???, .y = ???};

.x = (int[3]){0} seems to work, but I haven't found a way to assign y.

I tried .y = (int[3][2]){{0,0}, {0,0}, {0,0}} and several similar variant without success.

14
  • 2
    It's hard to say what's the best solution without knowing what you want to use this for. For example a single flexible array member which is accessed as if it was 1D array followed by a 2D array might be either sensible or clunky. Do you need elements to get allocated adjacently in memory or does that not matter at all? Also, should it be possible re-size these in run-time or not? Commented Feb 23, 2023 at 14:10
  • 1
    Why don't you write a function that returns an instance of xy_t with appropriate dynamic allocations. i.e. xy_t xy_t_create(unsigned int m, unsigned int n); You will also need a cleanup function to deallocate arrays. Commented Feb 23, 2023 at 14:25
  • Do you really need the syntactic sugar of y[a][b]? Cannot you use y[a + b*c] instead? Commented Feb 23, 2023 at 14:33
  • 2
    @AlbertShown I really thought the very tiresome "C does not have variable-length arrays" spam comments would slow down over time. But 24 years after the introduction of variable-length arrays in C they are still going strong... Commented Feb 23, 2023 at 15:00
  • 2
    @dbush It's has been a mandatory feature between years 1999 and 2011. Optional feature between years 2012 and 2023. Pointer-to-VLA once again mandatory from 2023, allowing objects of VLA type is still optional. Commented Feb 23, 2023 at 15:03

3 Answers 3

2

You can make member y a pointer to incomplete array type.

typedef struct {
  ...
  int       (*y)[]; // a pointer an array of unspecified length
} xy_t;

This would let initialize y with a compound literal.

xy_t xy;
xy.y = (int[3][2]){{0,0}, {0,0}, {0,0}};

However it will not be possible to dereference this 2D array because the type of xy.y is incomplete. This can be solved by assigning this pointer to a pointer to VLA with completed type.

int (*arr)[xy.N] = xy.y;

arr[i][j] = 42;
Sign up to request clarification or add additional context in comments.

4 Comments

I think your code is not C compliant, rather C++. But his question was about C
@Noname, no. It is plain C99. What makes you think that it is C++?
@Noname, it compiles perfectly in pedantic mode (godbolt.org/z/5o5j5T5P7). It even nicely optimizes the code.
ok get +1 then :)
1

int** is no longer a 2d array; it's a pointer to a pointer, or more specifically a pointer to an array of pointers in our case.

That's why you are having problems assigning a pointer to a 2d array ((int[3][2]){{0,0}, {0,0}, {0,0}}) to it.

To initialize int**, you would need something like

xy_t xy = {
   .M = 3,
   .N = 2,
   .x = (int[3]){ 0,0,0 },
   .y = (int*[3]){
      (int[2]){ 0,0 },
      (int[2]){ 0,0 },
      (int[2]){ 0,0 },
   },
};

You can also let the compiler do the counting:

xy_t xy = {
   .M = 3,
   .N = 2,
   .x = (int[]){ 0,0,0 },
   .y = (int*[]){
      (int[]){ 0,0 },
      (int[]){ 0,0 },
      (int[]){ 0,0 },
   },
};

Despite not being a 2d array, you can still access y[i][j] using xy.y[ i ][ j ].


The above structure needs M+2 pointers and M+3 memory blocks. If you wanted to make the structure smaller, you could use the following which only uses 2 pointers and 3 memory blocks:

typedef struct {
  const int M;
  const int N;
  int       *x;   // Should be 1-D M elements
  int       *y;   // 2-D M*N elements
} xy_t;

xy_t xy = {
   .M = 3,
   .N = 2,
   .x = (int[]){ 0,0,0 },
   .y = (int[]){ 0,0, 0,0, 0,0 },
};

You would access y[i][j] using xy.y[ i * xy.N + j ].

You could also cast it to a pointer to a 2d array in order to use y[i][j]. This is done as follows:

int (*y)[ xy.M ][ xy.N ] = (int(*)[ xy.M ][ xy.N ])xy.y;

You can make it even smaller! The following uses zero pointers and just one memory block.

typedef struct {
  const int M;
  const int N;
  int xy[];
} xy_t;

xy_t xy = {
   .M = 3,
   .N = 2,
   .xy = {
       0,0,0,
       0,0, 0,0, 0,0
   },
};

You would access x[i] using xy.xy[ i ].
You would access y[i][j] using xy.xy[ xy.M + i * xy.N + j ].

Or:

int (*x)[ xy.M ]         = (int(*)[ xy.M ])xy.xy;
int (*y)[ xy.M ][ xy.N ] = (int(*)[ xy.M ][ xy.N ])( xy.xy + xy.M );

x[i]
y[i][j]

That said, gcc warns

warning: initialization of a flexible array member [-Wpedantic]

So this last solution is probably non-standard.

1 Comment

Added (a lot) to my answer.
0

There is no easy way in C. There is no easy way even in C++ :) You will have to either:

  1. Declare somewhere fully initialized two-dimensional array and then use its name as the initalizer for your .y.
  2. Almost same, but with malloc of primary dimension (array of pointers) and another malloc (or multiple) for secondary dimension (each pointer in the array of pointers). This is what Tristan Riehs proposed above.

However if you are ready to give up on resizibility of both dimensions, you can use macro to simulate templates of C++, so that each concrete instance of your struct will be with fixed 2nd dimension.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.