具有灵活数组成员的结构并为其分配内存

Struct with flexible array member and allocating memory for it

我正在阅读由维基百科链接的 this version of the C99 standard 以试图了解灵活数组成员的工作原理。

在第 6.7.2.1 节中,声明了此结构:

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

并给出了一个例子:

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

它说 s1s2 的行为就好像声明是:

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

它列出了一些您可以做的事情:

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

我明白为什么上面的最后一行是未定义的,因为 s2 只分配了 6 个额外的字节,这不足以存储双精度数,但我不明白为什么它会说行为如果将 s1s2 声明为:

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

似乎 s2 没有分配足够的内存来存储该结构。

该文档似乎是某种草稿,所以我不确定是否有错误或者我是否误解了它的意思。

(您不应该再研究 C99,它已经过时了。C11 是文档 n1570,与您引用的位置相同。它将 probably/hopefully 很快被 C17 取代。)

我认为,它说它表现得好像只有一个元素的原因是短语

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...

我喜欢像对待 C++ 中的虚拟 类 一样对待 C99 中的灵活结构,您可以指向它们但不应该实例化它们。我经常构建一个辅助函数(工厂辅助函数?),它获取存储在灵活数组中的项目数量和 returns 结构所需的实际分配只是为了确保其意图清晰。

#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)); 

}

编译器知道 sizeof(myObjectSet_t) 至少需要 mod 4,因为 myObject_t 的数组可能需要至少 32 位对齐。我已经看到 Windows 上的 GNU 编译器 运行 在同一台笔记本电脑上的虚拟机中生成了与 GNU 编译器 运行 不同的填充。