Порядок свойств в нормальных объектах является сложным объектом в 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"
}
Таким образом, существует три сегмента, которые могут изменить порядок вставки (как это произошло в пример). И целые ключи не придерживаются порядка вставки.
Вопрос в том, какие методы этот порядок гарантирован в спецификации ES2015?
Следующие методы гарантируют показанный порядок:
Следующие методы / петли не гарантируют никакого порядка:
Вывод: даже в ES2015 вы не должны полагаться на порядок свойств нормального объектов в Javascript. Он подвержен ошибкам. Вместо этого используйте Map
.
C имеет концепцию неопределенного поведения, то есть некоторые языковые конструкции синтаксически допустимы, но вы не можете предсказать поведение при запуске кода.
Насколько мне известно, стандарт не говорит явно , почему существует концепция неопределенного поведения. На мой взгляд, это просто потому, что разработчики языка хотели иметь некоторую свободу действий в семантике, вместо того, чтобы требовать, чтобы все реализации обрабатывали целочисленное переполнение точно таким же образом, что, скорее всего, привело бы к серьезным потерям производительности, они просто оставили поведение undefined, так что если вы напишете код, вызывающий целочисленное переполнение, все может случиться.
Итак, имея это в виду, почему возникают эти «проблемы»? Язык четко говорит, что определенные вещи приводят к неопределенному поведению . Нет никаких проблем, нет никаких «следов». Если поведение undefined меняется, когда одна из задействованных переменных объявляется volatile
, это ничего не доказывает и не меняет. Это undefined ; вы не можете рассуждать о поведении.
Ваш самый интересный пример, с
u = (u++);
, является примером неопределенного поведения из учебника (см. статью в Википедии о точках последовательности ).
Я думаю, что соответствующие части стандарта C99 - это 6.5 Выражения, §2
Между предыдущей и следующей точкой последовательности объект должен иметь свое сохраненное значение изменяется не более одного раза при вычислении выражения. Кроме того, предыдущее значение должны читаться только для определения значения, которое будет сохранено.
и 6.5.16 Операторы присваивания, §4:
Порядок оценки операндов не определен. Если будет сделана попытка изменить результат оператора присваивания или для доступа к нему после следующей точки последовательности, поведение не определено.
Просто скомпилируйте и дизассемблируйте свою строку кода, если вы так хотите знать, как именно вы получаете то, что получаете.
Это то, что я получаю на своей машине, вместе с тем, что, как мне кажется, происходит:
$ 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 была своего рода оптимизацией компилятора?)