Search code examples
calsaallocalibalsa

alloca instead of local variable in alsa


I was using a sample C ALSA program as reference and ran along the following piece of code:

...
snd_ctl_event_t *event;
snd_ctl_event_alloca(&event);
...

Based on the ALSA source code, snd_ctl_event_alloca is a macro that calls __snd_alloca which is a macro that finally expands to the following equivalent line for snd_ctl_event_alloca(&event); (with some straightforward simplification):

event = (snd_ctl_event_t *) alloca(snd_ctl_event_sizeof());
memset(event, 0, snd_ctl_event_sizeof());

where snd_ctl_event_sizeof() is only implemented once in the whole library as:

size_t snd_ctl_event_sizeof()
{
    return sizeof(snd_ctl_event_t);
}

So my question is, isn't this whole process equivalent to simply doing:

snd_ctl_event_t event = {0};

For reference, these are the macros:

#define snd_ctl_event_alloca(ptr) __snd_alloca(ptr, snd_ctl_event)
#define __snd_alloca(ptr,type) do { *ptr = (type##_t *) alloca(type##_sizeof()); memset(*ptr, 0, type##_sizeof()); } while (0)

Clarifications:

  • The first block of code above is at the start of the body of a function and not in a nested block

EDIT

As it turns out (from what I understand), doing:

snd_ctl_event_t event;

gives a storage size of 'event' isn't known error because snd_ctl_event_t is apparently an opaque struct that's defined privately. Therefore the only option is dynamic allocation.


Solution

  • Since it is an opaque structure, the purpose of all these actions is apparently to implement an opaque data type while saving all the "pros" and defeating at least some of their "cons".

    One prominent problem with opaque data types is that in standard C you are essentially forced to allocate them dynamically in an opaque library function. It is not possible to implicitly declare an opaque object locally. This negatively impacts efficiency and often forces the client to implement additional resource management (i.e. remember to release the object when it is no longer needed). Exposing the exact size of the opaque object (through a function in this case) and relying on alloca to allocate storage is as close as you can get to a more efficient and fairly care-free local declaration.

    If function-wide lifetime is not required, alloca can be replaced with VLAs, but the authors probably didn't want/couldn't use VLAs. (I'd say that using VLA would take one even closer to emulating a true local declaration.)

    Often in order to implement the same technique the opaque object size might be exposed as a compile-time constant in a header file. However, using a function has an added benefit of not having to recompile the entire project if the object size in this isolated library changes (as @R. noted in the comments).


    Previous version of the answer (the points below still apply, but apparently are secondary):

    It is not exactly equivalent, since alloca defies scope-based lifetime rules. Lifetime of alloca-ed memory extends to the end of the function, while lifetime of local object extends only to the end of the block. It could be a bad thing, it could be a good thing depending on how you use it.

    In situations like

    some_type *ptr;
    
    if (some condition)
    {
      ...
      ptr = /* alloca one object */;
      ...
    }
    else
    {
      ...
      ptr = /* alloca another object */;
      ...
    }
    

    the difference in semantics can be crucial. Whether it is your case or not - I can't say from what you posted so far.

    Another unrelated difference in semantics is that memset will zero-out all bytes of the object, while = { 0 } is not guaranteed to zero-out padding bytes (if any). It could be important if the object is then used with some binary-based API's (like sent to a compressed I/O stream).