Несомненно, все программисты знают что использование выражений, подобных тому что приведено в заглавии поста, не то что нежелательно, а строго противопоказано. Такие конструкции, поведение компилятора в которых не определено, могут принести множество трудноуловимых ошибок и нежелательных последствий. Но уверен, многим начинающим программистам хотелось бы по глубже понять эту проблему и, заглянув за ширму компилятора, узнать что же именно происходит в таких случаях. Изучению одного из примеров подобного кода я и посвящаю этот пост. Добро пожаловать под кат :)Объект исследования
Для прим��ра, я разберу работу выражения "
++i + ++i" на двух различных языках: С и С#. Первый, как известно, компилируется в нативный код процессора, а второй, если грубо, работает на основе виртуальной стековой машины. И так, рассмотрим сами примеры:Исходник на Си:
- #include <stdio.h>
- void main()
- {
- int i = 5;
- i = ++i + ++i;
- printf("%d\n",i);
- }
И исходник на C#:
- using System;
- public class Test
- {
- public static void Main()
- {
- int i = 5;
- i = ++i + ++i;
- Console.WriteLine(i);
- }
- }
За ширмой Си
Воспользовавшись дизассемблером, посмотрим что-то же в итоге сгенерировал компилятор Си:
#A#5: int i = 5;
cs:0295 BE0500 mov si,0005
#A#6: i = ++i + ++i;
cs:0298 46 inc si
cs:0299 46 inc si
cs:029A 8BC6 mov ax,si
cs:029C 03C6 add ax,si
cs:029E 8BF0 mov si,ax
#A#7: printf("%d\n",i);
cs:02A0 56 push si
cs:02A1 B8AA00 mov ax,00AA
cs:02A4 50 push ax
cs:02A5 E8330C call _printf
Как видно из листинга, компилятор отобразил переменную
i на регистр SI процессора x86. После чего, дважды инкрементировав этот регистр, прибавил его самого к себе через аккумулятор AX. В результате, переменная i становится равной 14.За ширмой C#
С помощью Ildasm посмотрим что скрывается за C#:
.method public hidebysig static void Main() cil managed
{
.entrypoint
// Code size 21 (0x15)
.maxstack 3
.locals init (int32 V_0)
IL_0000: ldc.i4.5 // push 5 5
IL_0001: stloc.0 // i := pop() null
IL_0002: ldloc.0 // push i i
IL_0003: ldc.i4.1 // push 1 i, 1
IL_0004: add // push (pop() + pop()) (i+1) т.е. 6
IL_0005: dup // copy вершины стека 6, 6
IL_0006: stloc.0 // i := pop() // i := 6 6
IL_0007: ldloc.0 // push i 6, i
IL_0008: ldc.i4.1 // push 1 6, i, 1
IL_0009: add // push (pop() + pop()) 6, (i+1) т.е. 7
IL_000a: dup // copy вершины стека 6, 7, 7
IL_000b: stloc.0 // i := pop() // i := 7 6, 7
IL_000c: add // push (pop() + pop()) 13
IL_000d: stloc.0 // i := pop() null
IL_000e: ldloc.0 // push i i
IL_000f: call void [mscorlib]System.Console::WriteLine(int32)
IL_0014: ret
} // end of method Test::Main
Для наглядности, я добавил комментарии к ассемблеру виртуальной стековой машины. Смотря на листинг, можно увидеть что для инкремента переменной
i в стек помещается сама переменная и единица. Затем выполняется команда сложения которая, взяв два значения из стека и сложив, помещает результат опять в стек. Затем происходит дублирование вершины стека и запись значения обратно в переменную. Таким образом в стеке остается 5 + 1, т.е. 6. Далее цикл повторяется для другого инкремена: заносится в стек переменная, вслед за ней — единица, происходит сложение, дублирование вершины и запись результата второго инкремента назад в переменную. Теперь уже в i будет 7, а в стеке останутся 6 от первого случая и 7 от второго. Затем выполняется команда сложения и результат, равный теперь 13, заносится в переменную.Итоги
Вот такой вот, с виду одинаковый, код выполняется совершенно по разному в различных условиях. Избегайте подобного кода в своих программах.
Если тема вам показалась интересной — дайте знать, и я напишу еще пару занятных моментов из мира компиляции ;)
UPD. В комментариях подсказывают что в РНР, Java, Actionscript 3, JavaScript и TCL результат тоже равен 13, а вот в Perl, K++ и C GCC — 14 (но результат GCC может зависеть от настроек оптимизатора) :)
Отличились PL/SQL и Python — 10 (как подсказывает К.О. — из-за отсутствия инкрементов в языке), однако Bash — 12.
Так же есть компиляторы, непозволяющие написание подобного кода. Например Ruby (или вот так).
А каким будет результат в твоем, %username%, компиляторе?
UPD2. Ссылки по теме: точки следования Википедия, Алёна C++.
