эзотерический C, обозначающий сбой инициализатора, ошибку или функцию компилятора?

Я только что провел до часу ночи, отслеживая ошибку в моем коде, и то, что я нашел, меня очень удивило. Фактический код очень сложен, включая объединения структур, содержащих объединения структур и т. д., но я свел проблему к следующему упрощенному случаю отказа.

Происходит следующее: компилятор [gcc 5.4.0] меняет порядок выполнения назначенных инициализаторов, чтобы он соответствовал порядку их появления в структуре. Это не вызывает никаких проблем, пока вы инициализируете структуру с константами или переменными, которые не зависят от порядка. Проверьте следующий код. Это показывает, что компилятор явно переупорядочивает назначенные инициализаторы:

#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);
}

Результат выглядит следующим образом:

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

Я подозревал оптимизатор, но я попробовал тот же код, используя все возможные уровни оптимизации, и переупорядочение все еще происходит. Так что эта проблема в базовом компиляторе.

У меня есть решение, так что это скорее предупреждение для других и общий вопрос.

Кто-нибудь еще видел или заметил эту проблему?

Это ожидаемое/заданное поведение?


person Burt Wagner    schedule 16.01.2018    source источник
comment
Это чисто неопределенное поведение. Между инициализацией полей нет точки последовательности, и порядок инициализации не определен. А если серьезно, то надеюсь в жизни с таким кодом не столкнуться.   -  person Eugene Sh.    schedule 16.01.2018


Ответы (1)


Стандарт C99 позволяет применять побочные эффекты в любом порядке:

6.7.8.23: Порядок возникновения любых побочных эффектов среди выражений списка инициализации не определен.

В примечании содержится дополнительное разъяснение:

В частности, порядок оценки не обязательно должен совпадать с порядком инициализации подобъекта.

person Sergey Kalinichenko    schedule 16.01.2018
comment
И C2011 содержит эквивалентные положения в 6.7.9/23. и сноску 152. - person John Bollinger; 16.01.2018