How to put an allocated array (sizes known at runtime) in a struct?

107 Views Asked by At

To work on 3D arrays with unknown sizes at compile time, I wrote the following program that works :

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

typedef struct {
  int x,y,z;
} dim3D_t;

void print_array(dim3D_t *dim3D, int (*my_array)[dim3D->y][dim3D->z]) {
  for (int i_x=0; i_x<dim3D->x; i_x++)
    for (int i_y=0; i_y<dim3D->y; i_y++)
      for (int i_z=0; i_z<dim3D->z; i_z++)
        printf("%d %d %d : %d\n",i_x,i_y,i_z,my_array[i_x][i_y][i_z]);
}

void alloc_and_init_array(dim3D_t *dim3D, int (**my_array)[dim3D->y][dim3D->z]) {

  *my_array = malloc( sizeof(int) * dim3D->x * dim3D->y * dim3D->z );
  for (int i_x=0; i_x<dim3D->x; i_x++)
    for (int i_y=0; i_y<dim3D->y; i_y++)
      for (int i_z=0; i_z<dim3D->z; i_z++)
        (*my_array)[i_x][i_y][i_z]=100*i_x+10*i_y+i_z;
}

int main() {
  dim3D_t dim3D;
  int (*my_array)[dim3D.y][dim3D.z];
  
  scanf("%d %d %d", &dim3D.x, &dim3D.y, &dim3D.z);  
  alloc_and_init_array(&dim3D, &my_array);
        
  print_array(&dim3D, my_array);
}

I found very convenient to access the array elements with syntax like my_array[][][] and only one memory chunk is allocated.

But, now, I would like to have the array as a member in the structure. How can I do that ?

I would like to avoid 1D array (i.e. to add int *array in the structure member and to access to elements using my_struct.my_array[i_x*dim3D.y*dim3D.z+i_y*din3D.z+i_z]) or to have many memory chunk (arrays of int **, int *).

Edit 1 (answer in fact)

Following the Lundin answer and the good idea of the "cast" trick, I wrote a program that do not use FAM. So, I could have more than one array in my structure. It is very convenient. There is only line to add at the beginning of the functions.

This is the code

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

typedef struct {
  int x,y,z;
  int *array;
} dim3D_t;


void print_array(dim3D_t *dim3D) {

  int (*cast_dim3Darray)[dim3D->y][dim3D->z] = (int(*)[dim3D->y][dim3D->z]) dim3D->array; //cast
  
  for (int i_x=0; i_x<dim3D->x; i_x++)
    for (int i_y=0; i_y<dim3D->y; i_y++)
      for (int i_z=0; i_z<dim3D->z; i_z++)
        printf("%d %d %d : %d\n",i_x,i_y,i_z,cast_dim3Darray[i_x][i_y][i_z]);
}

void alloc_and_init_array(dim3D_t *dim3D) {

  dim3D->array = malloc( sizeof(int) * dim3D->x * dim3D->y * dim3D->z );
  
  int (*cast_dim3Darray)[dim3D->y][dim3D->z] = (int(*)[dim3D->y][dim3D->z]) dim3D->array; //cast
  
  for (int i_x=0; i_x<dim3D->x; i_x++)
    for (int i_y=0; i_y<dim3D->y; i_y++)
      for (int i_z=0; i_z<dim3D->z; i_z++)
        cast_dim3Darray[i_x][i_y][i_z]=100*i_x+10*i_y+i_z;
}

int main() {
  dim3D_t dim3D;
  
  scanf("%d %d %d", &dim3D.x, &dim3D.y, &dim3D.z);  
  alloc_and_init_array(&dim3D); 
        
  print_array(&dim3D);
  
}
3

There are 3 best solutions below

5
Lundin On BEST ANSWER

Flexible array members only work with one single dimension. However, it is safe to cast to/from them to array pointers since what matters for C's type system is that the variables are accessed through an lvalue of type int. It's a bit cumbersome to work with flexible array members here, since you need to cast. But quite possible.

Modified program with a bit of explanation in comments:

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

typedef struct {
  size_t x,y,z;
  int array[];
} dim3D_t;

// the function gets a little bit more readable with a struct:
void alloc_and_init_array (dim3D_t** dim3D, size_t x, size_t y, size_t z) {

  // watch out for people using sizeof *dim3D style here:
  *dim3D = malloc( sizeof(dim3D_t) + sizeof(int[x][y][z]) );
  if(*dim3D==NULL) { /* TODO error handling */ }

  (*dim3D)->x = x;
  (*dim3D)->y = y;
  (*dim3D)->z = z;

  // cast from the flexible array to an array pointer type with same element type:
  int (*arrptr)[y][z] = (int(*)[y][z]) (*dim3D)->array;

  for(size_t i=0; i<x; i++)
    for(size_t j=0; j<y; j++)
      for(size_t k=0; k<z; k++)
        arrptr[i][j][k] = 100*i + 10*j + k;
}

void print_array (const dim3D_t* dim3D) { // remember const correctness
  // cast here as well:
  int (*arrptr)[dim3D->y][dim3D->z] = (int(*)[dim3D->y][dim3D->z]) dim3D->array;  

  // I cooked up a bit more "3D:ish" output:
  for (size_t i_x=0; i_x<dim3D->x; i_x++)
  {
    printf("[ ");
    for (size_t i_y=0; i_y<dim3D->y; i_y++)
    {
      printf("[ ");
      for (size_t i_z=0; i_z<dim3D->z; i_z++)
      {
        printf("%3.1d ", arrptr[i_x][i_y][i_z]);
      }
      printf("] ");
    }
    printf("]\n");
  }
}


int main() {
  size_t x,y,z;
  scanf("%zu %zu %zu", &x, &y, &z);  
  // sanity check x, y, z here....

  dim3D_t* dim3D;
  alloc_and_init_array(&dim3D, x, y, z);
  print_array(dim3D);

  free(dim3D); // remember to clean up
}

Input:

2 2 3

Output:

[ [   0   1   2 ] [  10  11  12 ] ]
[ [ 100 101 102 ] [ 110 111 112 ] ]
1
Imagine Sign up For Comment On

Use a function to do the job for you.

Like that you can have only one allocation, and a easy way to access elements.

And that also allow you to handle out of bound errors.

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

typedef struct
{
    int     x;
    int     y;
    int     z;
}       point;

typedef struct
{
    point   dim;
    point * array;
}       t_array;

point * at(const t_array s, int x, int y, int z)
{
    // can check error here
    return (s.array + x + s.dim.x * y + z * s.dim.y * s.dim.x);
}

int main(void)
{
    t_array s;
    size_t  i;

    i = 0;
    s.dim.x = 3;
    s.dim.y = 3;
    s.dim.z = 3;
    s.array = malloc(sizeof(point) * s.dim.x * s.dim.y * s.dim.z);
    if (s.array == NULL)
        return (1);

    for (int i_x = 0 ; i_x < 3; i_x++)
        for (int i_y = 0; i_y < 3; i_y++)
            for (int i_z = 0; i_z < 3; i_z++)
                at(s, i_x, i_y, i_z)->x = i++;

    for (int i_x = 0 ; i_x < 3; i_x++)
        for (int i_y = 0; i_y < 3; i_y++)
            for (int i_z = 0; i_z < 3; i_z++)
                printf("%d %d %d : %d\n", i_x, i_y, i_z, at(s, i_x, i_y, i_z)->x);
}
3
arfneto On

Considering this struct and associated functions

typedef struct
{
    size_t x;
    size_t y;
    size_t z;
    int*** M;
} Matrix;

Matrix* so_create_m(size_t, size_t, size_t);
Matrix* so_destroy_m(Matrix*);
size_t  so_fill_m(Matrix*);
size_t  so_print_m(Matrix*, const char*);

And this program

int main(void)
{
    Matrix* my_mat = so_create_m(3, 4, 5);
    so_fill_m(my_mat);

    printf("\nMat[2][3][4] = %d\n\n", my_mat->M[2][3][4]);

    so_print_m(my_mat, "\n(test for 3,4,5)\n");
    so_destroy_m(my_mat);
    return 0;
}

that shows


Mat[2][3][4] = 234


(test for 3,4,5)
Matrix [3,4,5]

M[0]
000 001 002 003 004
010 011 012 013 014
020 021 022 023 024
030 031 032 033 034

M[1]
100 101 102 103 104
110 111 112 113 114
120 121 122 123 124
130 131 132 133 134

M[2]
200 201 202 203 204
210 211 212 213 214
220 221 222 223 224
230 231 232 233 234

A possible implementation follows...

Writing this way you can keep the usual array notation and keep the array inside Matrix.

code for the example

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

typedef struct
{
    size_t x;
    size_t y;
    size_t z;
    int*** M;
} Matrix;

Matrix* so_create_m(size_t, size_t, size_t);
Matrix* so_destroy_m(Matrix*);
size_t  so_fill_m(Matrix*);
size_t  so_print_m(Matrix*, const char*);

int main(void)
{
    Matrix* my_mat = so_create_m(3, 4, 5);
    so_fill_m(my_mat);

    printf("\nMat[2][3][4] = %d\n\n", my_mat->M[2][3][4]);

    so_print_m(my_mat, "\n(test for 3,4,5)\n");
    so_destroy_m(my_mat);
    return 0;
}

Matrix* so_create_m(size_t x, size_t y, size_t z)
{
    Matrix* mat = malloc(sizeof(Matrix));
    if (mat == NULL) return NULL;
    mat->x = x;
    mat->y = y;
    mat->z = z;
    mat->M = (int***)malloc(sizeof(int**) * x);
    if (mat->M == NULL)
    {
        free(mat);
        return NULL;
    }
    // now for the x[][]
    for (size_t ix = 0; ix < y; ix += 1)
    {
        mat->M[ix] = (int**)malloc(sizeof(int*) * y);
        if (mat->M[ix] == NULL)
        {  // could not allocate all
            for (size_t i = 0; i < ix; i += 1)
            {
                free(mat->M[i]);
                free(mat);
                return NULL;
            }
        }
        // now for the x[][][]
        for (size_t iy = 0; iy < y; iy += 1)
        {
            mat->M[ix][iy] = (int*)malloc(sizeof(int) * z);
            if (mat->M[ix][iy] == NULL)
            {  // could not allocate all
                for (size_t i = 0; i < iy; i += 1)
                {
                    free(mat->M[ix][i]);
                    for (size_t j = 0; j < ix; j += 1)
                        free(mat->M[j]);
                    free(mat->M);
                    free(mat);
                    return NULL;
                }
            }  // if()
        }      // for M[][]
    }          // for M[]
    return mat;
}

Matrix* so_destroy_m(Matrix* del)
{
    if (del == NULL) return NULL;
    // destroy M[x]
    for (size_t x = 0; x < del->x; x += 1)
    {
        // destroy M[x][y]
        for (size_t y = 0; y < del->y; y += 1)
            free(del->M[x][y]);
        free(del->M[x]);
    }
    // free(del->M);
    free(del);
    return NULL;
}

size_t so_fill_m(Matrix* mat)
{
    if (mat == NULL) return -1;
    for (size_t x = 0; x < mat->x; x += 1)
        for (size_t y = 0; y < mat->y; y += 1)
            for (size_t z = 0; z < mat->z; z += 1)
                mat->M[x][y][z] =
                    100 * (int)x + 10 * (int)y + (int)z;
    return 0;
}

size_t so_print_m(Matrix* mat, const char* msg)
{
    if (mat == NULL) return -1;
    if (msg != NULL) printf("%s", msg);
    printf(
        "Matrix [%llu,%llu,%llu]\n", mat->x, mat->y,
        mat->z);
    for (size_t x = 0; x < mat->x; x += 1)
    {
        printf("\nM[%llu]\n", x);
        for (size_t y = 0; y < mat->y; y += 1)
        {
            for (size_t z = 0; z < mat->z; z += 1)
                printf("%03d ", mat->M[x][y][z]);
            printf("\n");
        }
    }
    printf("\n");
    return 0;
}