c-preprocessorsizeofc99compound-literals

Double evaluation within macro: a case of sizeof() to determine array's size passed as compound literal


C99 makes it possible to define arrays basically anywhere, as compound literals.

For example, given a trivial function sumf() that accepts an array of float as input, we would expect the prototype to be : float sumf(const float* arrayf, size_t size);

This can then be used like that : float total = sumf( (const float[]){ f1, f2, f3 }, 3 );

It's convenient because there is no need to declare a variable beforehand. The syntax is slightly ugly, but this could be hidden behind a macro.

However, note the final 3. This is the size of the array. It is required so that sumf() knows where to stop. But as code ages and get refactored, it's also an easy source of errors, because now this second argument must be kept in sync with the first parameter definition. For example, adding f4 requires to update this value to 4, otherwise the function returns a wrong calculation (and there is no warning notifying this issue).

So it would be better to keep both in sync.

If it was an array which was declared through a variable, it would be easy. We could have a macro, that simplifies the expression like this : float total = sumf( ARRAY(array_f) ); with just #define ARRAY(a) (a) , sizeof(a) / sizeof(*(a)). But then, array_f must be defined before calling the function, so it's not longer a compound literal.

Since it's a compound literal, it has no name, so it can't be referenced. Hence I could not find any better way than to repeat the compound literal in both parameters.

#define LIST_F(...)  (const float*)( __VA_ARGS__) , sizeof((const float*)( __VA_ARGS__)) / sizeof(float)
float total = sumf ( LIST_F( f1, f2, f3 ) );

and this would work. Adding an f4 into the list would automatically update the size argument to correct size.

However, this all works fine as long as all members are variables. But what about cases where it's a function ? Would the function be invoked twice ? Say for example : float total = sumf ( LIST_F( v1, f2() ) );, will f2() be invoked twice? This is unclear to me as f2() is mentioned within sizeof(), so it could, in theory, know the return type size without actually invoking f2(). But I'm unsure what the standard says about that. Is there a guarantee ? Is it implementation dependent ?


Solution

  • will f2() be invoked twice?

    No, sizeof is not evaluated (unless it's a variable length array, but it's not).

    what the standard says about that. Is there a guarantee ?

    From C11 6.5.3.4p2:

    The sizeof operator yields the size (in bytes) of its operand, [...] If the type of the operand is a variable length array type, the operand is evaluated; otherwise, the operand is not evaluated and the result is an integer constant.

    Is it implementation dependent ?

    No, it should be always fine.

    Note that your other macro uses (const float*)(__VA_ARGS__), that will not work - the syntax is (float[]){ stuff }. Anyway, I would just do one macro, why two, too much typing. Just:

    #define SUMF_ARRAY(...)  \
         sumf( \
            (const float[]){__VA_ARGS__}, \
            sizeof((const float[]){__VA_ARGS__}) / sizeof(float))
    float total = SUMF_ARRAY(f1(), f2(), f3());