user1220978
user1220978

Reputation:

Is there a good reason why VLA are not permitted in pointers in structs?

Here is a way to define a Matrix type

typedef struct {
    int nr, nc;
    double *elem;
} Matrix;

I would like to define this

typedef struct {
    int nr, nc;
    double elem[nr][nc];
} Matrix;

It would be nice, because I would not have to worry about indexes. That's the reason VLA are useful in the first place, since they only do transparently what would be easy with index arithmetic.

Of course, the above is not possible, if only because the size of the struct would not be well defined. Then, I would still be happy with:

typedef struct {
    int nr, nc;
    double (*elem)[nc];
} Matrix;

Now, the matrix data is stored as a pointer, like in the non-VLA case. But the arithmetic could still be done by the compiler. The definition only tells it's some kind of pointer to double data, with the doubles arranged in an array of width nc.

It seems that it's not permitted either by the standard, and I wonder why, since it's easy to do the same by transtyping. For example, using the first definition (with double *), I could do

double get(Matrix *a, int i, int j) {
    int nc = a->nc;
    double (*p)[nc] = (double (*)[nc])a->elem;
    return p[i][j];
}

Of course, it's not very interesting here, since there is only one access to elem, but it could be if there are many.

So, my question, with the hope that it's on topic: what's the very reason of prohibiting the third definition?

I could imagine that it's dangerous since it's not guaranteed that nc handles the correct value, but this is dangerous anyway with pointers, so it does not look like a good reason.

Upvotes: 1

Views: 446

Answers (2)

supercat
supercat

Reputation: 81217

I believe that within a function in which a local variable nc is defined, you could use a typedef to create a local type double (*arr)[nc], and then cast a *double to that type. I believe such a cast would be legitimate for any *double that identifies a sufficiently-long sequence of double values, without regard for whether it was created using the same type as is defined within the function [if multiple functions each define their own array type, the compiler wouldn't recognize those types as equivalent, but it shouldn't matter]. I'm not 100% sure there wouldn't be Strict Aliasing issues, but I don't think there should be.

Otherwise, a fundamental difficulty is that a typedef involving a VLA creates a type using values that exist at a specific moment in time, and that can only occur for typedefs which are evaluated as executable statements, which in turn can only happen when typedefs are embedded within functions. Further, any identifiers used within array dimensions will be evaluated in the context of the enclosing function, rather than in the context of the partially-defined type.

Upvotes: 1

Jonathan Leffler
Jonathan Leffler

Reputation: 754520

Does this meet your requirements? It stores a void * in the structure, and the access functions cast that to a pointer to a 2D VLA and use that. GCC 5.2.0 on Mac OS X 10.10.5 compiles it cleanly, and valgrind (3.11.0-SVN from November 2014 or thereabouts) gives it a clean bill of health.

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

typedef struct
{
    int nr, nc;
    void *data;     // Actually double a[nr][nc]
} Matrix;

static double get(Matrix *a, int i, int j)
{
    double (*array)[a->nr][a->nc] = a->data;
    return (*array)[i][j];
}

static void set(Matrix *a, int i, int j, double v)
{
    double (*array)[a->nr][a->nc] = a->data;
    (*array)[i][j] = v;
}

static Matrix *mat_alloc(int nr, int nc)
{
    Matrix *m = malloc(sizeof(*m));
    if (m != 0)
    {
        m->nr = nr;
        m->nc = nc;
        m->data = malloc(nr * nc * sizeof(double));
        if (m->data == 0)
        {
            free(m);
            m = 0;
        }
    }
    return m;
}

static void mat_free(Matrix *m)
{
    free(m->data);
    free(m);
}

int main(void)
{
    int nr = 3;
    int nc = 5;

    Matrix *m = mat_alloc(nr, nc);
    if (m == 0)
    {
        fprintf(stderr, "Matrix allocation for %dx%d matrix failed\n", nr, nc);
        exit(1);
    }

    for (int i = 0; i < nr; i++)
    {
        for (int j = 0; j < nc; j++)
        {
            double v = (i * (nc + 1)) + j + 1;
            set(m, i, j, v);
            printf("Set: [%d,%d] = %4.1f\n", i, j, v);
        }
    }

    for (int j = 0; j < nc; j++)
    {
        for (int i = 0; i < nr; i++)
            printf("Get: [%d,%d] = %4.1f\n", i, j, get(m, i, j));
    }

    mat_free(m);
    return 0;
}

I'm not sure whether there's a neat way to lose the (*array) part of the notation in the access functions. I'd prefer it if there was one (other than using array[0][i][j], that is).

Example run

Set: [0,0] =  1.0
Set: [0,1] =  2.0
Set: [0,2] =  3.0
Set: [0,3] =  4.0
Set: [0,4] =  5.0
Set: [1,0] =  7.0
Set: [1,1] =  8.0
Set: [1,2] =  9.0
Set: [1,3] = 10.0
Set: [1,4] = 11.0
Set: [2,0] = 13.0
Set: [2,1] = 14.0
Set: [2,2] = 15.0
Set: [2,3] = 16.0
Set: [2,4] = 17.0
Get: [0,0] =  1.0
Get: [1,0] =  7.0
Get: [2,0] = 13.0
Get: [0,1] =  2.0
Get: [1,1] =  8.0
Get: [2,1] = 14.0
Get: [0,2] =  3.0
Get: [1,2] =  9.0
Get: [2,2] = 15.0
Get: [0,3] =  4.0
Get: [1,3] = 10.0
Get: [2,3] = 16.0
Get: [0,4] =  5.0
Get: [1,4] = 11.0
Get: [2,4] = 17.0

Upvotes: 4

Related Questions