Почему эти конструкции используют неопределенное поведение до и после приращения?

Порядок свойств в нормальных объектах является сложным объектом в Javascript.

Хотя в ES5 явно не указан порядок, ES2015 имеет порядок в определенных случаях. Это следующий объект:

o = Object.create(null, {
  m: {value: function() {}, enumerable: true},
  "2": {value: "2", enumerable: true},
  "b": {value: "b", enumerable: true},
  0: {value: 0, enumerable: true},
  [Symbol()]: {value: "sym", enumerable: true},
  "1": {value: "1", enumerable: true},
  "a": {value: "a", enumerable: true},
});

Это приводит к следующему порядку (в некоторых случаях):

Object {
  0: 0,
  1: "1",
  2: "2",
  b: "b",
  a: "a",
  Symbol(): "sym"
}
  1. целые ключи в порядке возрастания
  2. нормальные клавиши в порядке ввода
  3. Символы в порядке вставки

Таким образом, существует три сегмента, которые могут изменить порядок вставки (как это произошло в пример). И целые ключи не придерживаются порядка вставки.

Вопрос в том, какие методы этот порядок гарантирован в спецификации ES2015?

Следующие методы гарантируют показанный порядок:

  • Object.assign
  • Object.defineProperties
  • Object.getOwnPropertyNames
  • Object.getOwnPropertySymbols
  • ]
  • Reflect.ownKeys

Следующие методы / петли не гарантируют никакого порядка:

  • Object.keys
  • для ..in
  • JSON.parse
  • JSON.stringify

Вывод: даже в ES2015 вы не должны полагаться на порядок свойств нормального объектов в Javascript. Он подвержен ошибкам. Вместо этого используйте Map.

764
задан Sourav Ghosh 21 December 2018 в 11:42
поделиться

3 ответа

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

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

Итак, имея это в виду, почему возникают эти «проблемы»? Язык четко говорит, что определенные вещи приводят к неопределенному поведению . Нет никаких проблем, нет никаких «следов». Если поведение undefined меняется, когда одна из задействованных переменных объявляется volatile , это ничего не доказывает и не меняет. Это undefined ; вы не можете рассуждать о поведении.

Ваш самый интересный пример, с

u = (u++);

, является примером неопределенного поведения из учебника (см. статью в Википедии о точках последовательности ).

554
ответ дан 22 November 2019 в 21:19
поделиться

Я думаю, что соответствующие части стандарта C99 - это 6.5 Выражения, §2

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

и 6.5.16 Операторы присваивания, §4:

Порядок оценки операндов не определен. Если будет сделана попытка изменить результат оператора присваивания или для доступа к нему после следующей точки последовательности, поведение не определено.

61
ответ дан 22 November 2019 в 21:19
поделиться

Просто скомпилируйте и дизассемблируйте свою строку кода, если вы так хотите знать, как именно вы получаете то, что получаете.

Это то, что я получаю на своей машине, вместе с тем, что, как мне кажется, происходит:

$ cat evil.c
void evil(){
  int i = 0;
  i+= i++ + ++i;
}
$ gcc evil.c -c -o evil.bin
$ gdb evil.bin
(gdb) disassemble evil
Dump of assembler code for function evil:
   0x00000000 <+0>:   push   %ebp
   0x00000001 <+1>:   mov    %esp,%ebp
   0x00000003 <+3>:   sub    $0x10,%esp
   0x00000006 <+6>:   movl   $0x0,-0x4(%ebp)  // i = 0   i = 0
   0x0000000d <+13>:  addl   $0x1,-0x4(%ebp)  // i++     i = 1
   0x00000011 <+17>:  mov    -0x4(%ebp),%eax  // j = i   i = 1  j = 1
   0x00000014 <+20>:  add    %eax,%eax        // j += j  i = 1  j = 2
   0x00000016 <+22>:  add    %eax,-0x4(%ebp)  // i += j  i = 3
   0x00000019 <+25>:  addl   $0x1,-0x4(%ebp)  // i++     i = 4
   0x0000001d <+29>:  leave  
   0x0000001e <+30>:  ret
End of assembler dump.

(Я ... полагаю, что инструкция 0x00000014 была своего рода оптимизацией компилятора?)

76
ответ дан 22 November 2019 в 21:19
поделиться
Другие вопросы по тегам:

Похожие вопросы: