5

The CoreAudio framework uses a struct that is declared like this:

struct AudioBufferList
{
    UInt32      mNumberBuffers;
    AudioBuffer mBuffers[1]; // this is a variable length array of mNumberBuffers elements
};
typedef struct AudioBufferList  AudioBufferList;

As far as I can tell, this is basically a variable length collection of AudioBuffer structs. What is the 'correct' way to malloc such a struct?

AudioBufferList *list = (AudioBufferList *)malloc(sizeof(AudioBufferList));

Would this work?

I've seen all kinds of examples around the internet, like

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

or

malloc(sizeof(AudioBufferList) + sizeof(AudioBuffer) * (numBuffers - 1))
Jawap
  • 2,463
  • 3
  • 28
  • 46
  • "Would this work?" - only if `mNumberBuffers <= 1`. The other two examples allocate the same amount of memory each, if there is no padding after `mBuffers`. – M.M Mar 16 '14 at 00:01
  • possible duplicate of [iPhone: AudioBufferList init and release](http://stackoverflow.com/questions/3767527/iphone-audiobufferlist-init-and-release) – sbooth Mar 16 '14 at 02:30

3 Answers3

9

That's not a variable length array; it's a 'struct hack'. The standard (since C99) technique uses a 'flexible array member', which would look this:

struct AudioBufferList
{
    UInt32      mNumberBuffers;
    AudioBuffer mBuffers[]; // flexible array member
};

One of the advantages of the FAM is that your questions are 'irrelevant'; the correct way to allocate the space for numBuffer elements in the mBuffers array is:

size_t n_bytes = sizeof(struct AudioBufferList) + numBuffer * sizeof(AudioBuffer);
struct AudioBufferList *bp = malloc(nbytes);

To answer your question, in practice both the malloc() and the calloc() will allocate at least enough space for the job, but nothing in any C standard guarantees that the code will work. Having said that, compiler writers know that the idiom is used and usually won't go out of their way to break it.

Unless space is incredibly tight, it might be simplest to use the same expression as would be used with a FAM; at worst, you have a little more space allocated than you absolutely need allocated. It will continue to work when you upgrade the code to use a FAM. The expression used in the calloc() version would also work with a FAM member; the expression used in the malloc() version would suddenly be allocating too little space.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • This calculation for how many bytes to allocate is wrong - at least for the actual AudioBufferList definition. See Apple-provided implementation in my answer. – Adam Bryant Mar 16 '14 at 00:27
  • @Adam Bryant, can you elaborate on what you think is wrong with the number of bytes allocated? – M.M Mar 16 '14 at 00:32
  • See CalculateByteSize in my answer. You need to subtract sizeof AudioBuffer from sizeof AudioBufferList or else you'll be allocating an extra AudioBuffer. This is because the real definition of AudioBufferList is not using a flexible array member for mBuffers. – Adam Bryant Mar 16 '14 at 00:39
  • 1
    The code in your answer subtracts 1 because the `struct AudioBufferList` has an array of size 1 in it, but the `struct AudioBufferList` in this example has an array of size 0, so it should not subtract 1. – M.M Mar 16 '14 at 03:14
  • 1
    in other words, for the struct in this answer, `sizeof(struct AudioBufferList) == offsetof(struct AudioBufferList, mBuffers)` - that's how Flexible Array Member is defined. – M.M Mar 16 '14 at 03:19
3

Work out how much memory you need and malloc that amount. For example if you want 9 more AudioBuffers then

list = malloc( sizeof *list + 9 * sizeof list->mBuffers[0] );

This entire construct is non-portable by the way (the behaviour is undefined if they access beyond the bound of mBuffers, which is 1), but it used to be reasonably common.

Note that you should not cast the value returned by malloc. There's no benefit to be gained by doing so, but there is harm that can be done.

There is a standard construct which is similar; if you remove the 1 from the definition of AudioBufferList (and malloc one more unit). This is called "flexible array member".

M.M
  • 138,810
  • 21
  • 208
  • 365
  • Why should the return value of `malloc` not be casted? – sbooth Mar 16 '14 at 02:29
  • Casting it achieves nothing. But if you failed to `#include ` then the behaviour is undefined. Without a cast, the compiler must generate a diagnostic message. But with the cast, some compilers treat it as you saying "I know what I'm doing, I want to use whatever was in the `int` return register instead of the `void *` ", and I don't care that the function isn't declared yet" and don't give a message. See this thread: http://stackoverflow.com/questions/605845/do-i-cast-the-result-of-malloc – M.M Mar 16 '14 at 02:56
  • OK, you're using C and not C++ which led to my confusion. I haven't heard that argument before for straight C but it makes sense. – sbooth Mar 16 '14 at 11:33
2

The preferred way to do this is to use the Apple-provided CAAudioBufferList::Create function from CAAudioBufferList.cpp in the Core Audio Utility Classes. You can download the sources here:

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

Here is their implementation:

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;
}
Adam Bryant
  • 545
  • 3
  • 13
  • I'd rather stick with plain C, but that size calculation is definitely helpful, thanks. – Jawap Mar 16 '14 at 00:19
  • Okay, but the answer you marked as correct has a problem with the size calculation? – Adam Bryant Mar 16 '14 at 00:32
  • // This is a macro that does a sizeof and casts the result to a UInt32. This is useful for all the // places where -wshorten64-32 catches assigning a sizeof expression to a UInt32. // For want of a better place to park this, we'll park it here. #define SizeOf32(X) ((UInt32)sizeof(X)) – Adam Bryant Mar 16 '14 at 00:41
  • So it's just sizeof() – Adam Bryant Mar 16 '14 at 00:41
  • 1
    I think `offsetof` is much clearer than subtracting struct sizes but in the end the allocation should be the same. – sbooth Mar 16 '14 at 02:28