Kevin,
Это работает без JavaScript или CSS в большинстве браузеров:
<form>
<p><input type="text" name="field1" /></p>
<p><a href="previous.html">
<button type="button">Previous Page</button></a>
<button type="submit">Next Page</button></p>
</form>
Firefox, Opera, Safari, Google Chrome вся работа.
Как всегда, IE является проблемой.
Эта версия работает, когда JavaScript включен:
<form>
<p><input type="text" name="field1" /></p>
<p><a href="previous.html">
<button type="button" onclick="window.location='previous.html'">Previous Page</button></a>
<button type="submit">Next Page</button></p>
</form>
, Таким образом, дефект в этом решении:
Предыдущая страница не работает при использовании IE с JavaScript прочь.
Мышление Вы, кнопка "Назад" все еще работает!
Все, что
00401010 |> 99 /CDQ
00401011 |. 2BC2 |SUB EAX,EDX
00401013 |. D1F8 |SAR EAX,1
означает y / = 2
. Видите ли, автономный SAR
не будет выполнять целочисленное деление со знаком, как предполагали авторы компилятора. Стандарт C ++ 98 рекомендует , чтобы целочисленное деление со знаком округляло результат до 0, тогда как только SAR
округлял бы до отрицательной бесконечности. (Допускается округление в сторону отрицательной бесконечности, выбор остается на усмотрение реализации). Чтобы реализовать округление до 0 для отрицательных операндов, используется вышеуказанный трюк. Если вы используете беззнаковый тип вместо подписанного, то компилятор сгенерирует только одну инструкцию сдвига, так как проблема с отрицательным делением не возникнет.
Уловка довольно проста: для отрицательного y
расширение знака поместит образец 11111 ... 1
в EDX
, что на самом деле равно -1
в дополнении до 2 представление. Следующая SUB
фактически прибавит 1 к EAX
, если исходное значение y
было отрицательным. Если исходный y
был положительным (или 0), EDX
будет содержать 0
после расширения знака, а EAX
останется неизменным.
Другими словами, когда вы пишете y / = 2
со знаком y
, компилятор генерирует код, который делает что-то вроде следующего
y = (y < 0 ? y + 1 : y) >> 1;
или, лучше,
y = (y + (y < 0)) >> 1;
] Обратите внимание, что стандарт C ++ не требует округления результата деления до нуля, так что компилятор имеет право сделать только одну смену даже для подписанных типов. Однако обычно компиляторы следуют рекомендации для округления до нуля (или предлагают вариант для управления поведением).
PS Я не знаю наверняка, какова цель этого LEA
инструкция есть. Это действительно запретная игра. Однако я подозреваю, что это может быть просто инструкция-заполнитель, вставленная в код для дальнейшего исправления. Если я правильно помню, у компилятора MS есть опция, которая заставляет вставлять инструкции-заполнители в начале и в конце каждой функции. В будущем эта инструкция может быть перезаписана патчером с помощью инструкции CALL
или JMP
, которая будет выполнять код исправления. Этот конкретный LEA
был выбран только потому, что он производит команду-заполнитель правильной длины без операции. Конечно, это могло быть что-то совсем другое.
На самом деле это не ответ на вопрос, но полезный совет. Вместо того, чтобы возиться с OllyDbg.exe, вы можете заставить Visual Studio сгенерировать для вас asm-файл, который имеет дополнительный бонус, который он может поместить в исходный исходный код в качестве комментариев. Это не имеет большого значения для вашего текущего небольшого проекта, но по мере роста вашего проекта вы можете потратить немало времени на выяснение того, какой код сборки соответствует какому исходному коду.
Из командной строки вы хотите, чтобы Параметры / FAs и / Fa ( MSDN ).
Вот часть вывода для вашего примера кода (я скомпилировал код отладки, поэтому .asm длиннее, но вы можете сделать то же самое для своего оптимизированный код):
_wmain PROC ; COMDAT
; 8 : {
push ebp
mov ebp, esp
sub esp, 216 ; 000000d8H
push ebx
push esi
push edi
lea edi, DWORD PTR [ebp-216]
mov ecx, 54 ; 00000036H
mov eax, -858993460 ; ccccccccH
rep stosd
; 9 : int x=5; int y=1024;
mov DWORD PTR _x$[ebp], 5
mov DWORD PTR _y$[ebp], 1024 ; 00000400H
$LN2@wmain:
; 10 : while(x) { x--; y/=2; }
cmp DWORD PTR _x$[ebp], 0
je SHORT $LN1@wmain
mov eax, DWORD PTR _x$[ebp]
sub eax, 1
mov DWORD PTR _x$[ebp], eax
mov eax, DWORD PTR _y$[ebp]
cdq
sub eax, edx
sar eax, 1
mov DWORD PTR _y$[ebp], eax
jmp SHORT $LN2@wmain
$LN1@wmain:
; 11 : return x+y;
mov eax, DWORD PTR _x$[ebp]
add eax, DWORD PTR _y$[ebp]
; 12 : }
pop edi
pop esi
pop ebx
mov esp, ebp
pop ebp
ret 0
_wmain ENDP
Надеюсь, это поможет!
lea ebx, [ebx]
- это просто операция NOP. Его цель - выровнять начало цикла в памяти, что сделает его быстрее. Как вы можете видеть здесь, начало цикла начинается с адреса 0x00401010, который делится на 16. Благодаря этой инструкции
Операции CDQ
и SUB EAX, EDX
убедитесь, что при делении отрицательное число округляется до нуля, иначе SAR округлит его в меньшую сторону, что даст неверные результаты для отрицательных чисел.
Причина, по которой компилятор выдает это:
LEA EBX,DWORD PTR DS:[EBX]
вместо семантически эквивалентного:
NOP
NOP
NOP
NOP
NOP
NOP
.., заключается в том, что процессор быстрее выполняет одну 6-байтовую инструкцию, чем шесть 1- байтовые инструкции. Вот и все.