Search code examples
cperformancecompilationdynamic-memory-allocation

Using compound literals inside loop in C


Consider this code:

typedef struct my_struct { int a; int b; int c; } my_struct;

void some_function(my_struct *value)
{
    // do something
}

int main(void)
{
    for (;;) {
        some_function(&(my_struct){1, 2, 3});
    }

    return 0;
}

I plan to always set a, b and c to 1, 2 and 3. Therefore, the object passed to some_function will always be the same. In this case, does the compiler re-create and re-destroy the object at each iteration? Or does it convert it to something similar to this:

typedef struct my_struct { int a; int b; int c; } my_struct;

void some_function(my_struct *value)
{
    // do something
}

int main(void)
{
    my_struct once_and_for_all = {1, 2, 3};

    for (;;) {
        some_function(&once_and_for_all);
    }

    return 0;
}

Which doesnt do any memory allocation/deallocation in the loop, and runs at optimal speed.

I prefer the first syntax as it is less verbose, but I am also worried about performance.

I use GCC.


Solution

  • In this case, does the compiler re-create and re-destroy the object at each iteration?

    Semantically, yes. A compound literal appearing at block scope has automatic storage duration associated with the innermost containing block. Its lifetime ends when execution of that block terminates for any reason.

    But that doesn't put many constraints on the under-the-hood behavior of the implementation.

    You seem to be concerned about the cost of allocation and deallocation inside the loop, but allocation and deallocation of automatic objects is typically very fast. Often effectively free. On a stack-based machine, the memory used, if any, will be on the stack, and the same location will ordinarily be re-used on every loop iteration.

    The main difference between the two alternatives you present is that semantically, the former requires the object to be initialized on every loop iteration, whereas the latter must initialize it only once. Only if the compiler determines that the literal is not modified within the body of the loop, including indirectly by some_function(), can it consider lifting the initialization out of the loop to treat your first variation as if it were the second.

    If you want to encourage the compiler to make that assumption then you could declare some_function as ...

    void some_function(const my_struct *value);
    

    ... and define the literal as ...

    (const my_struct) { /* ...  */ }
    

    . If for some reason that's not semantically viable for you, then the optimization you're hoping for is inappropriate in the first place.

    I prefer the first syntax as it is less verbose, but I am also worried about performance.

    Such a concern is premature. Any performance difference between your two variations is likely to be too small too measure. Even if your structure were very large, so that you might actually be able to observe the cost of initializing it, you don't know whether that cost -- if even incurred -- is large enough to justify micro-optimization without measuring the program's performance and profiling it to see where it's spending its time.

    In general, write clean and clear code, using appropriate, efficient algorithms. Use available language features to express your intent in as much detail as you can. Enable compiler optimizations, and then don't worry about performance until you identify an actual performance problem.