Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

esoteric C designated initializer failure, compiler bug or feature?

I just spent until about 1AM tracking down a bug in my code and what I found really surprised me. The actual code is very complex involving unions of structures containing unions of structures, etc. but I have distilled the issue down to the following simplified failing case.

What is happening is that the compiler [gcc 5.4.0] is changing the order of the execution of the designated initializers to match the order they appear in the structure. This does not cause any issues as long as you are initializing the structure with constants or variables that have no order dependency. Check out this following code. It shows that the compiler clearly reorders the designated initializers:

#include <stdio.h>

typedef const struct {
    const size_t First_setToOne;
    const size_t Second_setToThree;
    const size_t Third_setToTwo;
    const size_t Fourth_setToFour;
} MyConstStruct;

static void Broken(void)
{
    size_t i = 0;

    const MyConstStruct myConstStruct = {
        .First_setToOne     = ++i,
        .Third_setToTwo     = ++i,
        .Second_setToThree  = ++i,
        .Fourth_setToFour   = ++i,
    };

    printf("\nBroken:\n");
    printf("First_setToOne    should be 1, is %zd\n", myConstStruct.First_setToOne   );
    printf("Second_setToThree should be 3, is %zd\n", myConstStruct.Second_setToThree);
    printf("Third_setToTwo    should be 2, is %zd\n", myConstStruct.Third_setToTwo   );
    printf("Fourth_setToFour  should be 4, is %zd\n", myConstStruct.Fourth_setToFour );
}

static void Fixed(void)
{
    size_t i = 0;

    const size_t First_setToOne     = ++i;
    const size_t Third_setToTwo     = ++i;
    const size_t Second_setToThree  = ++i;
    const size_t Fourth_setToFour   = ++i;

    const MyConstStruct myConstStruct = {
        .First_setToOne     = First_setToOne   ,
        .Third_setToTwo     = Third_setToTwo   ,
        .Second_setToThree  = Second_setToThree,
        .Fourth_setToFour   = Fourth_setToFour ,
    };

    printf("\nFixed:\n");
    printf("First_setToOne    should be 1, is %zd\n", myConstStruct.First_setToOne   );
    printf("Second_setToThree should be 3, is %zd\n", myConstStruct.Second_setToThree);
    printf("Third_setToTwo    should be 2, is %zd\n", myConstStruct.Third_setToTwo   );
    printf("Fourth_setToFour  should be 4, is %zd\n", myConstStruct.Fourth_setToFour );
}

int main (int argc, char *argv[])
{
    (void)argc;
    (void)argv;

    Broken();
    Fixed();

    return(0);
}

The output is as follows:

Broken:
First_setToOne    should be 1, is 1
Second_setToThree should be 3, is 2
Third_setToTwo    should be 2, is 3
Fourth_setToFour  should be 4, is 4

Fixed:
First_setToOne    should be 1, is 1
Second_setToThree should be 3, is 3
Third_setToTwo    should be 2, is 2
Fourth_setToFour  should be 4, is 4

I suspected the optimizer but I tried the same code using every possible optimization level and the reordering still occurs. So this issue is in the base compiler.

I have a solution so this is more of a warning for others and a general question.

Has anyone else ever see or noticed this issue?

Is this the expected/specified behavior?

like image 924
Burt Wagner Avatar asked Dec 23 '22 11:12

Burt Wagner


1 Answers

C99 standard allows side effects to be applied in any order:

6.7.8.23: The order in which any side effects occur among the initialization list expressions is unspecified.

A footnote provides further clarification:

In particular, the evaluation order need not be the same as the order of subobject initialization.

like image 84
Sergey Kalinichenko Avatar answered May 11 '23 00:05

Sergey Kalinichenko