eugenhu
eugenhu

Reputation: 1238

Struct with flexible array member and allocating memory for it

I was reading this version of the C99 standard linked by Wikipedia to try to understand how flexible array members work.

In section 6.7.2.1, this struct was declared:

struct s { int n; double d[]; };

And an example was given:

s1 = malloc(sizeof (struct s) + 10);
s2 = malloc(sizeof (struct s) + 6);

Where it said that s1 and s2 would behave as if the declarations were:

struct { int n; double d[1]; } *s1, *s2;

and it listed some things you can do:

double *dp;
dp = &(s1->d[0]); // valid
*dp = 42; // valid
dp = &(s2->d[0]); // valid
*dp = 42; // undefined behavior

I can see why the last line above is undefined since s2 was only allocated 6 extra bytes which is not enough to store a double, but then I don't understand why it would say that the behaviour of s1 and s2 would be like if they were declared as:

struct { int n; double d[1]; } *s1, *s2;

When it seems like s2 has not been allocated enough memory to store that struct.

The document seems to be some kind of draft so I'm not sure if there's been an error or if I'm misunderstanding what is meant.

Upvotes: 0

Views: 155

Answers (2)

bd2357
bd2357

Reputation: 794

I like to treat flexible structure in C99 like virtual classes in C++, you can point at them but you shouldn't instantiate them. I often build a helper function (factory helper?) that takes the number of items stored in the flexible array and returns the actual allocations needed for the structure just to make sure it is clear about the intent.

#define OBJECTS_NEEDED 10

typedef struct
{
    uint8_t val1;
    uint32_t val2; // probable forces 4byte alignment of structure 
} myObject_t;

typedef struct
{
    uint8_t allocatedObjects;
    uint16 someMetaData;
    myObject_t objects[];   // struct inherits worst case alignment rule
} myObjectSet_t;

size_t getMyObjectSetSize(uint8_t reqObjs)
{
    return sizeof(myObjectSet_t) + reqObjs * sizeof(myObject_t);
}

void initMyObjectSetSize(myObjectSet_t *mySet, uint8_t reqObjs) 
{
   mySet->allocatedObjects = reqObjs;
   // Other Init code .....
}

void main()
{
    myObjectSet_t *mySet = malloc(getMyObjectSetSize(OBJECTS_NEEDED));
    initMyObjectSetSize(mySet , OBJECTS_NEEDED);
    // One issue, you can't rely on structure padding to be the same
    // from machine to machine so this test may fail on one compiler
    // and pass on another. You really do nee to use offsetof() if 
    // you need to find address of mySet given the address of one
    // of the objects[]
    assert((void*)mySet->objects == (void*)(mySet + 1)); 

}

The compiler knows sizeof(myObjectSet_t) needs to be at least mod 4 because an array of myObject_t probable needs at least 32bit alignment. I have seen the GNU compiler running on Windows generate different padding from the GNU compiler running in a virtual machine on the same laptop.

Upvotes: 0

Jens Gustedt
Jens Gustedt

Reputation: 78903

(You shouldn't be looking into C99 anymore, it is obsolete. C11 is document n1570 at the same place that your are citing. It will probably/hopefully soon be replaced by C17.)

The reason, I think, that it says it behaves as if it had one element is the phrase

If it would have no elements, such an array behaves as if it had one element but the behavior is undefined if any attempt is made to access that element...

Upvotes: 1

Related Questions