0

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.

sshashank124
  • 31,495
  • 9
  • 67
  • 76
  • 1
    I wish kernel devs would more often give a bit of a comment on the rationale for these kinds of non-obvious techniques. – Michael Burr Oct 16 '18 at 00:33
  • 1
    Most often, the docs are just a 1-to-1 translation of the function name to English without much other description – sshashank124 Oct 16 '18 at 00:51

1 Answers1

2

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

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • hmm oh yeah, that's true. unfortunately in my case, it's the first line of the function – sshashank124 Oct 16 '18 at 00:01
  • @sshashank124: In that case the might be no real reason for using `alloca` and `memset` in your specific case. I might have been used for pure uniformity. It could be software-generated code, perhaps?.... – AnT stands with Russia Oct 16 '18 at 00:06
  • Yes I think it's probably software-generated code, the functions API across the different modules is very similar and possibly auto-generated – sshashank124 Oct 16 '18 at 00:07
  • 2
    You missed the whole point, which is that the size of the structure can change with new versions of the library without the application code having to be recompiled. – R.. GitHub STOP HELPING ICE Oct 16 '18 at 02:39
  • @R.: Good point. It I knew from the start that it was an opaque struct, I would've certainly realized what is really going on here. However, I see "not having to recompile" as icing-on-the-cake kind of thing. The primary objective is to "simulate" local declarations of opaque objects. – AnT stands with Russia Oct 16 '18 at 03:19
  • 1
    @AnT: ABI-compatibility is critical here. Otherwise installing a new version of the shared library would have it clobbering adjacent data on the caller's stack unless rigid versioning of the shared library were in place. Enforcing opaqueness, if it's even done, is much less important, and seeing that as "icing on the cake" would be a better perspective I think. – R.. GitHub STOP HELPING ICE Oct 16 '18 at 03:28
  • @R..: Yes, but they could've simply used a "classic" approach with `malloc`-ing the opaque objects inside some `snd_ctl_event_create` function and require the user to call some `snd_ctl_event_delete` at the end. And they'd easily have their decoupled translation and ABI-compatibility. No need to involve the stack at all. Yet, they made quite an effort to support local allocation. – AnT stands with Russia Oct 16 '18 at 03:33