Подтвердить что ты не робот

Странные значения при инициализации массива с использованием назначенных инициализаторов

Когда я инициализирую массив ниже всего вывода, выглядит нормально, за исключением values[3]. По какой-то причине values[3], инициализированный как values[0]+values[5], выводит очень большое число. Я предполагаю, что я пытаюсь назначить values[0]+values[5], прежде чем они будут правильно сохранены в памяти, но если кто-то может объяснить, что это было бы здорово.

int main (void)
{

    int values[10] = { 
        [0]=197,[2]=-100,[5]=350,
        [3]=values[0] + values[5],
        [9]= values[5]/10
    };

    int index;

    for (index=0; index<10; index++)
        printf("values[%i] = %i\n", index, values[index]);


    return 0;
}

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

values[0] = 197
values[1] = 0
values[2] = -100
values[3] = -1217411959
values[4] = 0
values[5] = 350
values[6] = 0
values[7] = 0
values[8] = 0
values[9] = 35
4b9b3361

Ответ 1

Похоже, вы подвержены неуказанному поведению здесь, так как порядок оценки выражений списка инициализации не указан, из стандартного раздела проекта C99 6.7.8:

Порядок, в котором возникают любые побочные эффекты между инициализацией список выражений неуказан. 133)

и примечание 133 говорит:

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

Насколько я могу судить, нормативный текст, подтверждающий примечание 133, будет из раздела 6.5:

За исключением случаев, указанных ниже [...], порядок оценки подвыражения и порядок, в котором происходят побочные эффекты, оба не определено.

и мы видим, что intializer является полным выражением от 6.8 (выделение мое):

Полное выражение - это выражение, которое не является частью другого выражения или декларатора. Каждое из следующего является полным выражением: инициализатор; [...]

Оглядываясь назад на один из моих старых ответов на С++, которые охватывали точки последовательности в инициализаторе и который помещает полное выражение в другое место, то я изначально сделал вывод, Я понял, что грамматика в 6.7.8 содержит инициализатор дважды:

initializer:
    assignment-expression
    { initializer-list }
    { initializer-list , }
initializer-list:
    designationopt initializer
    initializer-list , designationopt initializer

Я изначально не заметил этого и подумал о заявлении о полных выражениях, примененных к верхнему элементу в приведенной выше грамматике.

Теперь я считаю, что С++ полное выражение применяется к каждому инициализатору в списке инициализаторов, что делает мой предыдущий анализ неправильным.

Отчет о дефектах 439 подтвердил мое подозрение, что это действительно так, он содержит следующий пример:

#include <stdio.h>

#define ONE_INIT      '0' + i++ % 3
#define INITIALIZERS      [2] = ONE_INIT, [1] = ONE_INIT, [0] = ONE_INIT

int main()
{
    int i = 0;
    char x[4] = { INITIALIZERS }; // case 1
    puts(x);
    puts((char [4]){ INITIALIZERS }); // case 2
    puts((char [4]){ INITIALIZERS } + i % 2); // case 3
}

и он говорит:

При каждом использовании макроса INITIALIZERS переменная я увеличивается три раза. В случаях 1 и 2 поведение undefined отсутствует, потому что приращения находятся в выражениях, которые неопределенно секвенированы по отношению друг к другу, не без последствий.

поэтому каждый intializer в INITIALIZERS является полным выражением.

Поскольку этот отчет о дефектах против C11, стоит отметить, что C11 более подробный, чем C99 в нормативном тексте по этой проблеме, и он говорит:

Оценки выражений списка инициализации неопределенно упорядочены по отношению друг к другу и, следовательно, порядок, в котором происходят какие-либо побочные эффекты, неуказан. 152)

Существует поведение undefined в том случае, когда следующие выражения вычисляются до того, как соответствующим элементам в values присвоены:

 values[0] + values[5]

или

 values[5]/10

Это поведение undefined, поскольку использование неопределенного значения вызывает поведение undefined.

В этом конкретном случае самым простым способом было бы выполнить вычисления вручную:

int values[10] = { 
    [0]=197,[2]=-100,[5]=350,
    [3]= 197 + 350,
    [9]= 350/10
};

Существуют другие альтернативы, такие как выполнение присвоений элементу 3 и 9 после инициализации.

Ответ 2

Это не имеет никакого отношения к назначенным инициализаторам как таковым. Это та же ошибка, что и при попытке сделать что-то вроде этого:

int array[10] = {5, array[0]};

Порядок, в котором выполняются выражения списка инициализации, - это просто неопределенное поведение. Это означает, что он специфичен для компилятора, недокументирован и на него нельзя полагаться:

C11 6.7.9/23

Оценки выражений списка инициализации неопределенно упорядочены по отношению друг к другу и, следовательно, порядок, в котором происходят какие-либо побочные эффекты, не указывается.

Поскольку вы используете элементы массива для инициализации других членов массива, это означает, что вы должны изменить свой код на назначение времени выполнения вместо инициализации.

  int values[10];

  values[2] = -100;
  values[5] = 350;
  values[3] = values[0] + values[5];
  ...

В качестве побочного эффекта ваша программа также будет более читаема.

Ответ 3

Это первый случай, когда я видел что-то инициализированное таким образом, но я решил, что поведение, которое вы видите, связано с доступом к части массива, которая еще не была инициализирована. Поэтому я построил его с помощью GCC 4.6.3 в 32-разрядной системе Ubuntu 12.04. В моей среде я получил разные результаты, чем вы.

gcc file.c -o file

./file
values[0] = 197
values[1] = 0
values[2] = -100
values[3] = 197
values[4] = 0
values[5] = 350
values[6] = 0
values[7] = 0
values[8] = 0
values[9] = 35


objdump -d file > file.asm

cat file.asm     (relevant portion posted below)

080483e4 <main>:
 80483e4:   55                      push   %ebp
 80483e5:   89 e5                   mov    %esp,%ebp
 80483e7:   57                      push   %edi
 80483e8:   53                      push   %ebx
 80483e9:   83 e4 f0                and    $0xfffffff0,%esp
 80483ec:   83 ec 40                sub    $0x40,%esp
 80483ef:   8d 5c 24 14             lea    0x14(%esp),%ebx
 80483f3:   b8 00 00 00 00          mov    $0x0,%eax
 80483f8:   ba 0a 00 00 00          mov    $0xa,%edx
 80483fd:   89 df                   mov    %ebx,%edi
 80483ff:   89 d1                   mov    %edx,%ecx
 8048401:   f3 ab                   rep stos %eax,%es:(%edi)   <=====
 8048403:   c7 44 24 14 c5 00 00    movl   $0xc5,0x14(%esp)
 804840a:   00 
 804840b:   c7 44 24 1c 9c ff ff    movl   $0xffffff9c,0x1c(%esp)
 8048412:   ff
 8048413:   8b 54 24 14             mov    0x14(%esp),%edx
 8048417:   8b 44 24 28             mov    0x28(%esp),%eax
 804841b:   01 d0                   add    %edx,%eax
 804841d:   89 44 24 20             mov    %eax,0x20(%esp)
 8048421:   c7 44 24 28 5e 01 00    movl   $0x15e,0x28(%esp)
 8048428:   00 
 8048429:   8b 4c 24 28             mov    0x28(%esp),%ecx
 804842d:   ba 67 66 66 66          mov    $0x66666667,%edx
 8048432:   89 c8                   mov    %ecx,%eax
 8048434:   f7 ea                   imul   %edx
 8048436:   c1 fa 02                sar    $0x2,%edx
 8048439:   89 c8                   mov    %ecx,%eax
 804843b:   c1 f8 1f                sar    $0x1f,%eax

Я определил ключевую строку в приведенном выше выпуске, который, как я думаю, отмечает разницу между тем, что было создано вами и сгенерировано мной (отмечено с помощью < ======). Перед тем, как определенные элементы массива инициализируются с указанными вами значениями, my обнуляет содержимое массива. После этого происходит конкретная инициализация элементов массива.

Учитывая вышеизложенное поведение, я не думаю, что было бы необоснованно выдвигать гипотезу о том, что ваш пользователь не обнулял содержимое массива до инициализации определенных элементов массива. Какая разница в поведении? Я могу только догадываться; но я предполагаю, что мы используем две разные версии компилятора.

Надеюсь, что это поможет.

Ответ 4

int values[10] = { 
    [0]=197,[2]=-100,[5]=350,
    [3]=values[0] + values[5],
    [9]= values[5]/10
};

изменить

стандарт ISO C99, раздел 6.7.8 (Инициализация) указывает, что

Инициализация должна выполняться в порядке списка инициаторов, каждый инициализатор, предусмотренный для конкретного подобъекта, ранее указанный инициализатор для того же подобъекта; 132) все субобъекты, которые не инициализируются явно, должны быть инициализированы неявно то же, что и объекты, имеющие статическую продолжительность хранения

Но, как отметил Shafik, порядок оценки не должен соответствовать порядку инициализации

Это означает, что values[0] + values[5] может считывать значения мусора из:

  • values[0]
  • values[5] (это то, что происходит в вашем случае)
  • оба
  • ни один из них

Ответ 5

Попробуйте этот код:

int values[10];
values[0]=197;
values[2]=-100;
values[5]=350;
values[3]=values[0]+values[5];
values[9]=values[5]/10;

И затем вы печатаете массив, как вы это делали.