5

How do you specify a dynamic array of static arrays in C?
I want to make a struct holding two dynamic arrays of static arrays.

struct indexed_face_set {
    double * [3] vertices;
    int * [3] faces;
};

This should hold a dynamic list of vertices, which are each 3 doubles, and a dynamic list of faces, which are each 3 ints.

tshepang
  • 12,111
  • 21
  • 91
  • 136
user1858467
  • 71
  • 1
  • 5

2 Answers2

3

The syntax is, well, C's approach to declarations is not the cleanest and C++ inherited that...

double (*vertices)[3];

That declaration means that vertices is a pointer to double [3] objects. Note that the parenthesis are needed, otherwise (as in double *vertices[3]) it would mean an array of 3 double*.

After some time you end up getting use to the inverted way of parenthesis on expressions...

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • Might be better to use `double(**verticies)[3]` so that growing the outer array means copying pointers instead of copying arrays. The more so if the dimension of the inner array might be larger than three. Of course, that'll make the *access* syntax really gruesome. – dmckee --- ex-moderator kitten Nov 28 '12 at 03:47
  • 1
    @dmckee: The answer tackles the problem in the question. Regarding the copies (if the outer array does need to grow), the cost of copying a pointer vs. 3 `int` or 3 `double` is not *that* much, and can still be done with `memcpy`. Unless profiling determined that this is a high cost in the program I would not complicate the already complex code. I would otherwise suggest creating a struct that holds the vertices/faces and then have a dynamic array of those... as that would simplify the code (under the assumption that both arrays in the question have the same size) – David Rodríguez - dribeas Nov 28 '12 at 12:58
0

For the specific case of a structure containing two arrays each of dimension 3, it would be simpler to make the arrays a part of the structure, rather than dynamically allocating them separately:

struct indexed_face_set
{
    double vertices[3];
    int    faces[3];
};

However, there certainly could be cases where it makes sense to handle dynamic array allocation. In that case, you need a pointer to an array in the structure (and not an array of pointers). So, you would need to write:

struct indexed_face_set
{
    double (*vertices)[3];
    int    (*faces)[3];
};

To allocate a complete struct indexed_face_set, you need to use something like new_indexed_face_set() and to free one you need to use something like destroy_indexed_face_set():

struct indexed_face_set *new_indexed_face_set(void)
{
    struct indexed_face_set *new_ifs = malloc(sizeof(*new_ifs));
    if (new_ifs != 0)
    {
        double (*v)[3] = malloc(sizeof(*v));
        int    (*f)[3] = malloc(sizeof(*f));
        if (v == 0 || f == 0)
        {
            free(v);
            free(f);
            free(new_ifs);
            new_ifs = 0;
        }
        else
        {
            new_ifs->vertices = v;
            new_ifs->faces = f;
        }
    }
    return(new_ifs);
}

void destroy_indexed_face_set(struct indexed_face_set *ifs)
{
    if (ifs != 0)
    {
        free(ifs->vertices);
        free(ifs->faces);
        free(ifs);
    }
}

Then you can use it like this:

void play_with_ifs(void)
{
    struct indexed_face_set *ifs = new_indexed_face_set();
    if (ifs != 0)
    {
        (*ifs->vertices)[0] = 3.14159;
        (*ifs->vertices)[1] = 2.71813;
        (*ifs->vertices)[2] = 1.61803;
        (*ifs->faces)[0] = 31;
        (*ifs->faces)[1] = 30;
        (*ifs->faces)[2] = 29;
        do_something_fancy(ifs);
        destroy_indexed_face_set(ifs);
    }
}

Note that the notation using pointers to arrays is moderately messy; one reason why people do not often use them.

You could use this fragment as the body of a header:

#ifndef DASS_H_INCLUDED
#define DASS_H_INCLUDED

struct indexed_face_set;
extern void play_with_ifs(void);
extern void do_something_fancy(struct indexed_face_set *ifs);
extern void destroy_indexed_face_set(struct indexed_face_set *ifs);
extern struct indexed_face_set *new_indexed_face_set(void);

#endif /* DASS_H_INCLUDED */

It doesn't need any extra headers included; it does not need the details of the structure definition for these functions. You'd wrap it in suitable header guards.


Because the code above is a bit messy when it comes to using the arrays, most people would use a simpler notation. The header above can be left unchanged, but the code could be changed to:

struct indexed_face_set
{
    double *vertices;
    int    *faces;
};

struct indexed_face_set *new_indexed_face_set(void)
{
    struct indexed_face_set *new_ifs = malloc(sizeof(*new_ifs));
    if (new_ifs != 0)
    {
        double *v = malloc(3 * sizeof(*v));
        int    *f = malloc(3 * sizeof(*f));
        if (v == 0 || f == 0)
        {
            free(v);
            free(f);
            free(new_ifs);
            new_ifs = 0;
        }
        else
        {
            new_ifs->vertices = v;
            new_ifs->faces = f;
        }
    }
    return(new_ifs);
}

void destroy_indexed_face_set(struct indexed_face_set *ifs)
{
    if (ifs != 0)
    {
        free(ifs->vertices);
        free(ifs->faces);
        free(ifs);
    }
}

void play_with_ifs(void)
{
    struct indexed_face_set *ifs = new_indexed_face_set();
    if (ifs != 0)
    {
        ifs->vertices[0] = 3.14159;
        ifs->vertices[1] = 2.71813;
        ifs->vertices[2] = 1.61803;
        ifs->faces[0] = 31;
        ifs->faces[1] = 30;
        ifs->faces[2] = 29;
        do_something_fancy(ifs);
        destroy_indexed_face_set(ifs);
    }
}

This is much simpler to understand and use and would generally be regarded as more idiomatic C.

Since the size of each array is fixed, there's no particular need to record the size in the structure. If the sizes varied at runtime, and especially if some indexed face sets had, say, 8 vertices and 6 faces (cuboid?), then you might well want to record the sizes of the arrays in the structure. You'd also specify the number of vertices and number of faces in the call to new_indexed_face_set().

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278