Странное поведение в VB6 при расчете отрицательного числа Mod число, которое является степенью 2 - PullRequest
6 голосов
/ 01 ноября 2019

Я отлаживал старый кусок кода, разработанный в VB6, и нашел что-то очень странное. Это можно продемонстрировать с помощью следующего простого кода:

Private Sub Command1_Click()
    Dim a As Integer
    Dim b As Integer
    Dim c As Integer

    a = 0
    Text1.Text = a - 1
    Text2.Text = CStr((a - 1) Mod 4)

    b = 0
    b = b - 1
    Text3.Text = b
    Text4.Text = CStr(b Mod 4)

    c = 0 - 1
    Text5.Text = c
    Text6.Text = CStr(c Mod 4)

End Sub

Вот как выглядит форма:

enter image description here

Можно подуматьпосле нажатия кнопки Text2, Text4 и Text6 должны отображать одинаковое содержимое, равное -1. Это тот случай, когда я нажал F5, чтобы запустить его в IDE.

Вот как это выглядело, когда я запустил его в IDE:

enter image description here

Странная вещь произошла, когда я сделал exe из IDE и запустил сам exe. Text1 и Text6 показывали -1, но Text4 показывал 3.

Вот как это выглядело, когда я запускал exe вне IDE:

enter image description here

Это происходит, только если второй операнд имеет степень 2. Когда я изменил 4 на 5, все текстовые поля показывали -1.

Я проверил это на 2 компьютерах с Windows 10 и получил то же самоерезультат.

Я знаю, что VB6 старый, и не так много людей все еще имеют его в наличии для тестирования. Я ценю, если кто-то может помочь мне понять это.

Спасибо.

1 Ответ

2 голосов
/ 05 ноября 2019

Это похоже на ошибку компилятора. Компилятор не может распознать, что значение подписано в случае Integer (16-битное значение), но соблюдает его в случае Long.

Кодкоторый выполняет по модулю 4 почти одинаков в каждом случае и следует оптимизированному шаблону для мощностей по модулю 2 :

Long (b Mod 4&):

or   eax, 0xFFFFFFFF  # eax = 0xFFFFFFFF (which is -1)
and  eax, 0x80000003  # eax = 0x80000003 The modulo op, note it's signed because of the 8

jns  other_code       # Skip the next three lines if the result is non-negative (it isn't here)

dec  eax              # eax = 0x80000002
or   eax, 0xFFFFFFFC  # eax = 0xFFFFFFFE
inc  eax              # eax = 0xFFFFFFFF (which is -1)

other_code:

В конце eax содержит 0xFFFFFFFF, который равен -1, который передается для отображения.


Целое число (b Mod 4):

or   eax, 0xFFFFFFFF  # eax = 0xFFFFFFFF, ax = 0xFFFF (which is -1 in both cases)
and  ax,  0x3         # eax = 0xFFFF0003, ax = 0x0003. Should have been "and ax, 0x8003!"

jns  other_code       # Skip the next three lines if the result is non-negative (it incorrectly is)

dec  ax               # Skipped
or   ax,  0xFFFC      # Skipped
inc  ax               # Skipped

other_code:

В конце eax содержит 0xFFFF0003 и затем передается в функцию __vbaStrI2, которая, очевидно, игнорирует два старших байта и использует только 0003.

Если вместо and ax, 0x3 был использован and ax, 0x8003, топропущенные строки сработают и преобразуют 0xFFFF0003 в 0xFFFFFFFF, что равно -1.


При отключенных оптимизациях поразрядная математика по модулю заменяется простым делением:

sub   ax, 0x1          # b = b - 1
mov   cx, 0x4          # Prepare division by 4
idiv  cx               # Integer division

Что касается того, почему случаи для a и c работают так, как ожидалось, это потому, что компиляторвычисляет -1 Mod 4 на этапе компиляции и жестко кодирует результат в исполняемом файле:

push  0xFFFFFFFF       # Pass hardcoded -1 for display

other_code:

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...