如何使用malloc分配一个包含可变长度数组的结构体?

5
CoreAudio框架使用一个声明如下的结构体:
struct AudioBufferList
{
    UInt32      mNumberBuffers;
    AudioBuffer mBuffers[1]; // this is a variable length array of mNumberBuffers elements
};
typedef struct AudioBufferList  AudioBufferList;

据我所知,这基本上是一个长度可变的 AudioBuffer 结构体集合。分配这样一个结构体的“正确”方法是什么?
AudioBufferList *list = (AudioBufferList *)malloc(sizeof(AudioBufferList));

你觉得这个可行吗?

我在互联网上看到了各种各样的例子,比如:

calloc(1, offsetof(AudioBufferList, mBuffers) +
          (sizeof(AudioBuffer) * numBuffers))

或者
malloc(sizeof(AudioBufferList) + sizeof(AudioBuffer) * (numBuffers - 1))

如果 mNumberBuffers <= 1,这个会起作用吗?另外两个例子每个都分配相同数量的内存,如果在 mBuffers 后没有填充。 - M.M
可能是重复的问题:iPhone:AudioBufferList init和release - sbooth
3个回答

9
那不是一个可变长度数组,而是一种“结构体技巧”。自从C99标准以来,通常会使用“柔性数组成员”技术,它看起来像这样:
struct AudioBufferList
{
    UInt32      mNumberBuffers;
    AudioBuffer mBuffers[]; // flexible array member
};

FAM的一个优势是你的问题是“不相关的”;在mBuffers数组中为numBuffer元素分配空间的正确方法是:
size_t n_bytes = sizeof(struct AudioBufferList) + numBuffer * sizeof(AudioBuffer);
struct AudioBufferList *bp = malloc(nbytes);

回答你的问题,实际上malloc()calloc()都会至少分配足够的空间来完成任务,但是在任何C标准中都没有保证代码一定能够运行。尽管如此,编译器编写者知道这种习惯用法并且通常不会特意破坏它。
除非空间非常紧张,否则使用与FAM相同的表达式可能是最简单的方法;最坏的情况下,会比你绝对需要的空间多分配一点。当你升级代码以使用FAM时,它仍然可以正常工作。在calloc()版本中使用的表达式也适用于FAM成员;而在malloc()版本中使用的表达式将突然分配过少的空间。

感谢您详细的回答 :-) - Jawap
这个计算分配多少字节的方法是错误的 - 至少对于实际的AudioBufferList定义来说是如此。请参见我的答案中提供的Apple实现。 - Adam Bryant
@Adam Bryant,您能详细说明一下您认为分配的字节数有什么问题吗? - M.M
请查看我的回答中的CalculateByteSize函数。你需要从AudioBufferList的大小中减去AudioBuffer的大小,否则你将会多分配一个AudioBuffer。这是因为AudioBufferList的真实定义并没有使用mBuffers作为可变数组成员。 - Adam Bryant
1
你回答中的代码会减去1,因为“struct AudioBufferList”中有一个大小为1的数组,但是这个例子中的“struct AudioBufferList”有一个大小为0的数组,所以不应该减去1。 - M.M
1
换句话说,对于这个答案中的结构体,sizeof(struct AudioBufferList) == offsetof(struct AudioBufferList, mBuffers) - 这就是 Flexible Array Member 的定义。 - M.M

3
计算需要多少内存,然后使用malloc分配相应的内存。例如,如果您需要9个额外的AudioBuffers,则:
list = malloc( sizeof *list + 9 * sizeof list->mBuffers[0] );

顺便说一下,整个结构不具有可移植性(如果他们访问mBuffers的边界之外,即1,其行为是未定义的),但它曾经相当普遍。

请注意,您不应将malloc返回的值强制转换。这样做没有好处,但会产生危害。

有一种类似的标准结构;如果从AudioBufferList的定义中删除1(并分配一个更多的单元)。这被称为“柔性数组成员”。


为什么不应该将malloc的返回值强制转换? - sbooth
强制类型转换并不能实现任何东西。但是如果你没有包含 #include <stdlib.h>,那么行为就是未定义的。没有强制类型转换,编译器必须生成一条诊断消息。但是有了强制类型转换,一些编译器会将其视为你在说“我知道自己在做什么,我想使用 int 返回寄存器中的任何内容而不是 void *”,并且我不关心该函数是否已经声明过了”,并且不会给出任何消息。请参见此线程:https://dev59.com/dHRB5IYBdhLWcg3wgHWr - M.M
好的,你正在使用C而不是C++,这让我感到困惑。我以前从未听说过这种关于纯C的论点,但它很有道理。 - sbooth

2

最好的方法是使用苹果提供的CAAudioBufferList.cpp中的CAAudioBufferList::Create函数,它属于Core Audio实用程序类。您可以在此处下载源代码:

https://developer.apple.com/library/mac/samplecode/CoreAudioUtilityClasses/Introduction/Intro.html

以下是它们的实现:

AudioBufferList*    CAAudioBufferList::Create(UInt32 inNumberBuffers)
{
    UInt32 theSize = CalculateByteSize(inNumberBuffers);
    AudioBufferList* theAnswer = static_cast<AudioBufferList*>(calloc(1, theSize));
    if(theAnswer != NULL)
    {
        theAnswer->mNumberBuffers = inNumberBuffers;
    }
    return theAnswer;
}

void    CAAudioBufferList::Destroy(AudioBufferList* inBufferList)
{
    free(inBufferList);
}

UInt32  CAAudioBufferList::CalculateByteSize(UInt32 inNumberBuffers)
{
    UInt32 theSize = SizeOf32(AudioBufferList) - SizeOf32(AudioBuffer);
    theSize += inNumberBuffers * SizeOf32(AudioBuffer);
    return theSize;
}

我宁愿坚持使用普通的C语言,但是那个尺寸计算方法确实很有帮助,谢谢。 - Jawap
好的,但是你标记为正确的答案在大小计算方面存在问题? - Adam Bryant
// 这是一个宏,用于将sizeof的结果转换为UInt32。这对于所有-wshorten64-32捕获将sizeof表达式分配给UInt32的地方很有用。 // 由于没有更好的地方来放置它,我们将把它放在这里。 #define SizeOf32(X) ((UInt32)sizeof(X)) - Adam Bryant
所以它只是sizeof()函数 - Adam Bryant
1
我认为offsetof比减去结构体大小更清晰,但最终分配应该是相同的。 - sbooth

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接