Странное поведение ф-ции Timer

Программирование на Visual Basic, главный форум. Обсуждение тем программирования на VB 1—6.
Даже если вы плохо разбираетесь в VB и программировании вообще — тут вам помогут. В разумных пределах, конечно.
Правила форума
Темы, в которых будет сначала написано «что нужно сделать», а затем просьба «помогите», будут закрыты.
Читайте требования к создаваемым темам.
Mikle
Изобретатель велосипедов
Изобретатель велосипедов
Аватара пользователя
 
Сообщения: 4148
Зарегистрирован: 25.03.2003 (Вт) 14:02
Откуда: Туапсе

Странное поведение ф-ции Timer

Сообщение Mikle » 09.03.2017 (Чт) 11:05

Простой код:
Код: Выделить всё
  Dim t As Single

  t = Timer
  Caption = Timer - t

Почему он возвращает отрицательные значения?
Если просто в цикле проверять возвращаемое значение Timer, оно никогда не убывает.
Я начал сомневаться, что Timer возвращает тип Single, и это ошибки округления, но, во-первых, почему ошибка всегда отрицательная? Во-вторых, из справки:
Возвращает значение типа Single, представляющее количество секунд, прошедших после полночи.

В-третьих, такой код работает аналогично:
Код: Выделить всё
  Dim t As Single

  CopyMemory t, Timer, 4
  Caption = Timer - t

С помощью CopyMemory я убедился, что возвращаемое значение именно в формате Single.
Однако такой код, почему-то ведёт себя правильно (возвращает нули, теоретически иногда должен вернуть положительное число):
Код: Выделить всё
  Dim t As Single

  t = Timer
  Caption = CSng(Timer) - t

alibek
Большой Человек
Большой Человек
 
Сообщения: 14205
Зарегистрирован: 19.04.2002 (Пт) 11:40
Откуда: Russia

Re: Странное поведение ф-ции Timer

Сообщение alibek » 09.03.2017 (Чт) 11:45

А что показывает такой код?
Код: Выделить всё
t0 = Timer
t1 = Timer
Caption = "Time: " & t1-t0 & " (t0 = " & t0 & ", t1=" & t1 & ")"
Lasciate ogni speranza, voi ch'entrate.

Mikle
Изобретатель велосипедов
Изобретатель велосипедов
Аватара пользователя
 
Сообщения: 4148
Зарегистрирован: 25.03.2003 (Вт) 14:02
Откуда: Туапсе

Re: Странное поведение ф-ции Timer

Сообщение Mikle » 09.03.2017 (Чт) 12:00

alibek писал(а):А что показывает такой код?

Такой код работает, как положено, t0 и t1 почти всегда равны, разница = 0.

alibek
Большой Человек
Большой Человек
 
Сообщения: 14205
Зарегистрирован: 19.04.2002 (Пт) 11:40
Откуда: Russia

Re: Странное поведение ф-ции Timer

Сообщение alibek » 09.03.2017 (Чт) 12:06

Видимо проделки оптимизатора.
А если переменную t в первоначальном коде объявить как Long?
Либо вместо CSng использовать просто скобки вокруг функции?
Lasciate ogni speranza, voi ch'entrate.

Mikle
Изобретатель велосипедов
Изобретатель велосипедов
Аватара пользователя
 
Сообщения: 4148
Зарегистрирован: 25.03.2003 (Вт) 14:02
Откуда: Туапсе

Re: Странное поведение ф-ции Timer

Сообщение Mikle » 09.03.2017 (Чт) 12:12

alibek писал(а):А если переменную t в первоначальном коде объявить как Long?

Наверное, имелось ввиду Double? Тогда погрешность есть, но она бывает то положительной, то отрицательной.
alibek писал(а):вместо CSng использовать просто скобки вокруг функции?

Скобки не влияют.

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16478
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: Странное поведение ф-ции Timer

Сообщение Хакер » 09.03.2017 (Чт) 12:15

Это глобальная проблема: не только для VB6, а для практически всех языков, где есть функция, возвращающая время с точностью до долей секунды. Очень часто можно найти в интернете вопросы PHP-шников, что разница двух последовательных вызовов функции microtime() оказывается отрицательной (например).

Причина являения в том, что поток достаётся то одному ядру, то другому, то третьему на МП-системе, а у разных ядер «счётчики» тикают не синхронно.

Установи process affinity mask процессу так, чтобы он всё время выполнялся на одном ядре. В диспетчере задач или программно. Если проблема не исчезнет — будем копать.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

Mikle
Изобретатель велосипедов
Изобретатель велосипедов
Аватара пользователя
 
Сообщения: 4148
Зарегистрирован: 25.03.2003 (Вт) 14:02
Откуда: Туапсе

Re: Странное поведение ф-ции Timer

Сообщение Mikle » 09.03.2017 (Чт) 14:42

Хакер писал(а):Причина являения в том, что поток достаётся то одному ядру, то другому, то третьему на МП-системе, а у разных ядер «счётчики» тикают не синхронно.

Ты не вник в описанное.
1. Разница всегда отрицательна, что не объяснить рассинхроном ядер.
2. Каким образом CSng(Timer) избавит от рассинхрона?

Кроме того, я проверил, привязка процесса к одному ядру проблему не решает, привязывал процесс EXE через Диспетчер задач.

The trick
Постоялец
Постоялец
 
Сообщения: 781
Зарегистрирован: 26.06.2010 (Сб) 23:08

Re: Странное поведение ф-ции Timer

Сообщение The trick » 09.03.2017 (Чт) 15:03

Я думаю это из-за округления.
Первый вызов Timer сохраняет значение во временную переменную одинарной точности.
Второй вызов вычитает это значение из стека FPU которое в 80 битовом формате. К примеру:
Безымянный.png
Безымянный.png (7.19 Кб) Просмотров: 10293

Видно что в FPU содержится 57523.257000000000000 а во временной переменной округленное до 57523.26. Вычитаем - получаем отрицательное значение.
UA6527P

Mikle
Изобретатель велосипедов
Изобретатель велосипедов
Аватара пользователя
 
Сообщения: 4148
Зарегистрирован: 25.03.2003 (Вт) 14:02
Откуда: Туапсе

Re: Странное поведение ф-ции Timer

Сообщение Mikle » 09.03.2017 (Чт) 15:16

А каким образом CopyMemory корректно переносит из 80-битного формата в Single?

nouyana
Продвинутый пользователь
Продвинутый пользователь
Аватара пользователя
 
Сообщения: 114
Зарегистрирован: 29.01.2016 (Пт) 17:42

Re: Странное поведение ф-ции Timer

Сообщение nouyana » 09.03.2017 (Чт) 15:28

Mikle писал(а):CopyMemory

Возможно, дополнительная функция затрачивает дополнительное время, и разница, даже с учётом округления, становится положительной.

The trick
Постоялец
Постоялец
 
Сообщения: 781
Зарегистрирован: 26.06.2010 (Сб) 23:08

Re: Странное поведение ф-ции Timer

Сообщение The trick » 09.03.2017 (Чт) 15:42

Mikle писал(а):А каким образом CopyMemory корректно переносит из 80-битного формата в Single?

Нет. CopyMemory ты делаешь копировании из какой-то ячейки памяти, а не из fpu стека т.е. значение сохраняется куда-то в формате Single.
UA6527P

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16478
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: Странное поведение ф-ции Timer

Сообщение Хакер » 09.03.2017 (Чт) 16:25

Ладно, немного расследования:

Вот такая функция часто возвращает отрицательные значения:
Код: Выделить всё
Public Function GetTdiff1() As Single     
    Dim t1 As Single
    t1 = Timer
    GetTdiff1 = Timer - t1
End Function

Вот такая не возвращает отрицательные значения:
Код: Выделить всё
Public Function GetTdiff2() As Single 
    Dim t1 As Single
    Dim t2 As Single
    t1 = Timer
    t2 = Timer
    GetTdiff2 = t2 - t1
End Function


Разница в скомпилированном коде такая:
code_diff_1.png
code_diff_1.png (12.41 Кб) Просмотров: 10280


Здесь fstsw ax это сохранение в регистр AX слова-состояние сопроцессора, а test AL, 0d — проверка бита (флага), одним словом это обработка ошибок после выполнения вычитательной операции и на результат эти инструкции влиять не могут.

Поэтому я ставлю чекбокс «Remove Floating Point Error Checks» в свойстах проекта, чтобы избавиться от инструкций, проверяющих ошибки FP-арифметики, и компилирую проект опять:
code_diff_2.png
code_diff_2.png (9.05 Кб) Просмотров: 10280

Теперь лишних инструкций (fstsw, test, jnz) нет, и всего лишь визуально проще смотреть на код.

Зато разница в поведении сохранилась: по прежнему GetTdiff1 возвращает часто отриц. числа, а GetTDiff2 — нет.

В чём разница? В том, что во втором случае первый (левый) операнд операции вычитания сначала загоняется из FP-стека в обычный стек (инструкция FST), и тут из обычного стека загоняается опять в FP-стек (инструкция FLD). Иными словами: туда-сюда передёрнули FP-число, при этом с ним произошли некоторые манипуляции (округление).

В GetTDiff1 только правый операнд подвергается переносу из FP-стека в обычный стек, а в GetTDiff2 — и левый операнд, и правый.

Значит округлению в GetTDiff1 подвергается только правый аргумент, а в GetTDiff2 — оба аргумента.

В доказательство пойдём в свойствах проекта ещё поставим чекбокс «Allow Unrounded Floating Point Operations». Компилируем. При таких настройках компиляции обе функции транслируются в одинаковый машинный код, линкер при этом два одинаковых куска машинного кода мерджит в один:
code_nodiff.png
code_nodiff.png (3.91 Кб) Просмотров: 10280

code_merged_comdats.png
code_merged_comdats.png (3.61 Кб) Просмотров: 10280

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

Вывод: ошибка вычисления появляется из-за того, что один операнд подвергатся округлению, а другой — нет. Похоже, что это баг компилятора, ведь если чекбокс «Allow Unrounded Operations» не снят, компилятор по идее должен гарантировать, что все операнды подвергаются округлению.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

TheWatcher
Новичок
Новичок
Аватара пользователя
 
Сообщения: 29
Зарегистрирован: 27.08.2012 (Пн) 0:53
Откуда: Республика Беларусь

Re: Странное поведение ф-ции Timer

Сообщение TheWatcher » 09.03.2017 (Чт) 21:51

Методология и выводы Хакера неоспоримы.

От себя добавлю, что, помимо PHP, точно такой же глюк присущ всем версиям неоптимизирующих компиляторов PowerBASIC и интерпретатора ThinBasic, написанного на PB. К счастью, существуют и иные диалекты БЕЙСИКА, где предприняты специальные меры для корректного округления (приведения) всех операндов к типу Single (ака float) перед "вычитательной" операцией. :)
TheWatcher
=========
3.6GHz Core i5-3470, 16GB RAM / GTX 1060, 6GB VRAM
x86 Win XP Pro Sp3 / x64 Win 7 Ult Sp1 / x64 Ubuntu 16.04

Mikle
Изобретатель велосипедов
Изобретатель велосипедов
Аватара пользователя
 
Сообщения: 4148
Зарегистрирован: 25.03.2003 (Вт) 14:02
Откуда: Туапсе

Re: Странное поведение ф-ции Timer

Сообщение Mikle » 10.03.2017 (Пт) 9:32

Благодарю, очень прояснило.
Для меня большим сюрпризом оказалось, что это работает с внешними функциями, вызванными из dll.
Хакер писал(а):ошибка вычисления появляется из-за того, что один операнд подвергатся округлению, а другой — нет. Похоже, что это баг компилятора, ведь если чекбокс «Allow Unrounded Operations» не снят, компилятор по идее должен гарантировать, что все операнды подвергаются округлению.

Я вообще ожидал обратного, что округление будет в любом случае, даже когда установлена опция «Allow Unrounded Operations», ведь это уже скомпилированная функция, находящаяся в dll.

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16478
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: Странное поведение ф-ции Timer

Сообщение Хакер » 10.03.2017 (Пт) 9:44

Mikle писал(а):Я вообще ожидал обратного, что округление будет в любом случае, даже когда установлена опция «Allow Unrounded Operations», ведь это уже скомпилированная функция, находящаяся в dll.


Так окугление лежит на плечах «принимающей стороны» (то есть вызывающей стороны). Уже скомпилированная готовая функция возвращает результат не в обычном регистре, в не памяти, а в ячейке FPU-стека. А ячейка FPU-стека имеет 80-битную ширину.

Вот это действительно интересный момент: если есть пара stdcall-функций, возвращающих Single (float) или Double (double), то обе возвращают значение на верхушке FPU-стека, но очевидно, с разной точностью. На чьих плечах должно лежать округление согласно конвенции: на вызывающей или на вызываемой?
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

TheWatcher
Новичок
Новичок
Аватара пользователя
 
Сообщения: 29
Зарегистрирован: 27.08.2012 (Пн) 0:53
Откуда: Республика Беларусь

Re: Странное поведение ф-ции Timer

Сообщение TheWatcher » 10.03.2017 (Пт) 20:46

Хакер писал(а):Так окугление лежит на плечах «принимающей стороны» (то есть вызывающей стороны).


Это почему же? Как при сборке автор библиотеки в своем заголовочном файле возврат функции пропишет, так кодогенератор компилятора библиотечный код и сгенерит. Если Currency (правда, нет такого типа в ИСО Си), то вся 80-битная ST0-ячейка FPU будет считаться валидной. Если double, то с шестнадцатой, а если float, то с седьмой позиции после запятой и ниже просто мусор будет, но в машинном коде появятся дополнительные операции округления: либо через control word и переключение FPU в нужный режим округления, либо через явное предписание двойной/одинарной точности в мнемонике матопераций (в листинге из Олли -- одинарной) в теле функции, либо через дамп-перезагрузку (fstp [DWORD PTR]/fld [DWORD PTR] для одинарной точности, [QWORD PTR] для двойной) перед возвратом. Либо в любом наборе из перечисленного -- тут уж как разработчик компилятора/оптимизатора свой кодогенератор наваял (хехе, всякое бывает в погоне за невъ...нной, но не совместимой с другими языками/диалектами точностью).

В любом случае при разработке клиента (вызывающей стороны) следует пользоваться авторскими (родными) библиотечными хедерами, чтобы клиенту на приёме мусора из невалидных битов ST0-ячейки не нахватать, поскольку всё то же самое относится и к этапу кодогенерации клиента -- в другое время и в другом месте. И касается это в равной степени и STDCALL-, и CDECL-соглашения; тут они как раз не различимы.
TheWatcher
=========
3.6GHz Core i5-3470, 16GB RAM / GTX 1060, 6GB VRAM
x86 Win XP Pro Sp3 / x64 Win 7 Ult Sp1 / x64 Ubuntu 16.04

The trick
Постоялец
Постоялец
 
Сообщения: 781
Зарегистрирован: 26.06.2010 (Сб) 23:08

Re: Странное поведение ф-ции Timer

Сообщение The trick » 10.03.2017 (Пт) 23:06

Хакер писал(а):На чьих плечах должно лежать округление согласно конвенции: на вызывающей или на вызываемой?

По логике на вызываемой. По идее, раз функция rtcGetTimer возвращает именно 32 битное число, то и в стек она должна класть его.
TheWatcher писал(а): Если Currency (правда, нет такого типа в ИСО Си), то вся 80-битная ST0-ячейка FPU будет считаться валидной.

Currency вообще к стеку FPU отношения не имеет.
UA6527P

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16478
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: Странное поведение ф-ции Timer

Сообщение Хакер » 11.03.2017 (Сб) 9:45

TheWatcher писал(а):И касается это в равной степени и STDCALL-, и CDECL-соглашения; тут они как раз не различимы.


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

Если в вызываемую функцию передаётся ссылка на COM-интерфейс, кто должен вызвать IUnknown::AddRef — вызывающая сторона перед передачей, или вызываемая сторона? Если передаётся строка BSTR, то вызывающая сторона передаёт как есть, а вызываемая создаёт копию, если ей надо, или же вызывающая сторона вызывает SysAllocString, и сразу передаёт вызываемой? Это пример высокоуровневых проблем.

Есть более низкоуровневые проблемы: что stdcall, что cdecl, что fastcall в большинстве случаев возвращают результат в регистре EAX. Но как быть, если размер типа возврата меньше, чем размер EAX? Скажем, если мы возвращаем unsigned short или signed char? Понятно, что значащие биты попадают в AX или AL, это не подлежит сомнению. Но как вызываемая функция должна поступить с незначащими битами?

Оставить их как попало? (Тогда вызывающая сторона, если кастует char к int, должны быть выполнить movzx/movsx)
Занулить?
Провести sign-extension (если тип возврата — знаковый)? (Тогда можно сразу использовать значение)

Всегда есть несколько вариантов. И чтобы код, написанный разными людьми и скомпилированный разными компиляторами, стыковался и мог взаимодействовать, все подобные вопросы должен быть отрегулированы соглашением о вызове. Поэтому не надо говорить, что тут всё зависит от кодогенератора и фантазии его автора — кодогенератор сгенерирует так, как требует calling convention.

The trick писал(а): По идее, раз функция rtcGetTimer возвращает именно 32 битное число, то и в стек она должна класть его.

Но тогда отчасти теряется выгодна от использования стека FPU для возвратов.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

TheWatcher
Новичок
Новичок
Аватара пользователя
 
Сообщения: 29
Зарегистрирован: 27.08.2012 (Пн) 0:53
Откуда: Республика Беларусь

Re: Странное поведение ф-ции Timer

Сообщение TheWatcher » 12.03.2017 (Вс) 0:59

Хакер писал(а):Если в вызываемую функцию передаётся ссылка на COM-интерфейс, кто должен вызвать IUnknown::AddRef — вызывающая сторона перед передачей, или вызываемая сторона? Если передаётся строка BSTR, то вызывающая сторона передаёт как есть, а вызываемая создаёт копию, если ей надо, или же вызывающая сторона вызывает SysAllocString, и сразу передаёт вызываемой? Это пример высокоуровневых проблем.


Приведенные Вами выше примеры c COM и BSTR — это не "межправительственные соглашения", а "условия использования" этих технологий, выдвигаемые их монопольным разработчиком и потребителем — Microsoft Windows. Так что хочешь-не хочешь, а как автор сказал "кто COM-объект создаёт, тот и инкрементирует/декрементирует счетчик экземпляров; кто BSTR-строку выделил, тот и освобождает", — так и будет.

Хакер писал(а):Есть более низкоуровневые проблемы: что stdcall, что cdecl, что fastcall в большинстве случаев возвращают результат в регистре EAX. Но как быть, если размер типа возврата меньше, чем размер EAX? Скажем, если мы возвращаем unsigned short или signed char? Понятно, что значащие биты попадают в AX или AL, это не подлежит сомнению. Но как вызываемая функция должна поступить с незначащими битами? Оставить их как попало? (Тогда вызывающая сторона, если кастует char к int, должны быть выполнить movzx/movsx) Занулить? Провести sign-extension (если тип возврата — знаковый)? (Тогда можно сразу использовать значение)


Для взаимодействия ассемблерных процедур вообще не существует понятий STDCALL, CDECL, FASTCALL и прочих соглашений: где и как автор параметры передать захотел (хошь — в регистрах, хошь — в стеке, а хошь — и где-нить в выделенной памяти), там и так их и принял, или не принял, или вообще местами поменял.

А в Си — да, соглашения есть, и в VB они есть, ибо на Си написан, и в Си транслирует, и пограничные соглашения вроде соблюдает.

Ну, и вот "на стыке миров", на межмодульных/межъязыковых границах — да, хотелось бы видеть унификацию.

Хакер писал(а):Всегда есть несколько вариантов. И чтобы код, написанный разными людьми и скомпилированный разными компиляторами, стыковался и мог взаимодействовать, все подобные вопросы должен быть отрегулированы соглашением о вызове.


Вот тут логично. Но по факту — не все и не всегда отрегулированы бывают. К примеру, Билл Гейтс в своём VC давно игнорирует линуксоидный десятибайтный long double (а по факту — 80-битный extended-precision floating point) и благополучно "пакует" свой собственный в восьмибайтный double. Отсюда, кстати, и в VB-листингах ничего шире QWORD PTR Вы не увидите, поскольку, как Вы правильно обращали всеобщее внимание, VB — это всего лишь слегка "подфуфыренный" VC.

Хакер писал(а):Поэтому не надо говорить, что тут всё зависит от кодогенератора и фантазии его автора — кодогенератор сгенерирует так, как требует calling convention.


Требует? Не боги горшки обжигают. Любой кодогенератор сгенерирует прежде всего то, что заложил в него автор сообразно целевой платформе, своему мастерству и коммерческой выгоде. Пример приведен выше.

Хакер писал(а):Но тогда отчасти теряется выгодна от использования стека FPU для возвратов.


Не факт, прежде всего, что она есть: всё опять-таки зависит от того, выгодно ли это с точки зрения кодогенерации/оптимизации или нет. Примеров много — тысячи их — и в компиляторах модульной архитектуры типа Вашего подзащитного VB6, использующих сторонний бэк-энд, и в монолитных, где никакой трансляции ни в какое промежуточное представление нет, как в PB, FBSL, Oxygen Basic, thinBasic, TinyC Compiler и др.

The trick писал(а):Currency вообще к стеку FPU отношения не имеет.


Обратите внимание, что в ответном расследовании Хакера речь идет не только и не столько о том, что и где возвращает функция, а о том, как она поступает с операндами.

Так вот, потрудитесь набросать и скомпилить что-нить простенькое, типа
Код: Выделить всё
Private Function Profit() As Currency
    Dim dollar As Currency
    dollar = 100# ' cents
    Profit = dollar
End Function
Теперь гляньте в Олли, что там VB6 выдаст? Ага, MSVBVM60.__vbaFpCy. Так может, это что-то про F[loating] p[oint] и C[urrenc]y? Гляньте теперь, что ж там в рантаймной процедуре? Один сплошной FPU-стек кругом, да еще и QWORD PTR впридачу.


С уважением,
Последний раз редактировалось TheWatcher 13.03.2017 (Пн) 20:17, всего редактировалось 1 раз.
TheWatcher
=========
3.6GHz Core i5-3470, 16GB RAM / GTX 1060, 6GB VRAM
x86 Win XP Pro Sp3 / x64 Win 7 Ult Sp1 / x64 Ubuntu 16.04

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16478
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: Странное поведение ф-ции Timer

Сообщение Хакер » 12.03.2017 (Вс) 10:13

TheWatcher писал(а):Но приведенные Вами выше примеры c COM и BSTR — это не "межправительственные соглашения", а "условия использования" этих технологий, выдвигаемые их монопольным разработчиком и потребителем — Microsoft Windows. Так что хочешь-не хочешь, а как автор сказал...

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

TheWatcher писал(а):Для взаимодействия ассемблерных процедур вообще не существует понятий STDCALL, CDECL, FASTCALL и прочих соглашений: где и как автор параметры передать захотел (хошь — в регистрах, хошь — в стеке, а хошь — и где-нить в выделенной памяти), там и так их и принял, или не принял, или вообще местами поменял.

Ну... зачем писать здесь эти очевидные вещи? Или есть подозрение, то я «не в курсе», что при написании кода на ассемблере можно не следовать никаким соглашениям, и придумывать свои собственные хоть для каждого отдельного вызова какой-то процедуры? Я сам недавно писал при отладке драйвера принтера про процедуру cblt, которая единственный аргумент принимает через регистр EDI:
viewtopic.php?f=9&t=56110&p=6790391#cblt_callconv_revealed

TheWatcher писал(а):А в Си — да, соглашения есть, и в VB они есть, ибо на Си написан, и в Си транслирует, и пограничные соглашения вроде соблюдает.

Это неправильный взгляд на мир, абсолютно. Соглашения есть не в Си или в VB. Соглашения есть вообще, в отрыве от каких-то языков. Они будут актуальны для любого языка, и для ассемблера в том числе, если код пишут хотя бы два человека (а как правило — две команды), и один другому должен как-то сообщить правила игры, по которым один участок кода будет взаимодействовать с другим.

Да даже если один человек пишет код, и даже если он его пишет на ассемблере, всё равно, если у него там есть вызов, хотя бы один, есть и соглашение о вызове. Оно может быть самодельным, оно может быть одноразовым, но это всё равно соглашение — пусть и с самим собой, что человек сам с собой договорился следовать четко определённым правилам игры, когда будет свою же собственную функцию вызывать, чтобы не получилось путаницы.

TheWatcher писал(а): ибо на Си написан, и в Си транслирует

Первое правда, а второе — абсолютная неправда.

TheWatcher писал(а):Вот оно ровно так и тикает, как в весьма конкретный кодогенератор VC весьма конкретные авторы всё напридумали и заложили, да от постороннего взгпяда обфусцировали и антиотладочных мин понаставили, и погоняли и пофиксили, и на бумажку себе записали, чтоб в ногу себе не стрелять при дальнейшей поддержке, а бумажку ту в сейф аккуратно положили, и Билл тот сейф тщательно запер. Ибо нефиг, потому как цена вопроса — миллиарды.

Глупость какая-то. Да и вы теперь сами себе противоречите.
VB написан на C и C++ (хотя куски написаны на ассемблере — к сведению!), скомпилирован MS-овским компилятором VC. Так. Рантайм, вернее функция rtcGetTimer написана на Си, её код был скомпилирован компилятором VC.

Вопрос: VC следует стандартам? Или же генерируемый им код в вопросах правил передачи аргументов и возврата значений придерживается засекреченных? Если вы убеждены во втором, то я даже не буду продолжать этот разговор: я не занимаюсь развенчанием теорий заговора.

TheWatcher писал(а):VB — это всего лишь слегка "подфуфыренный" VC. И что?

Откуда ноги растут у этого заблуждения? Ничего подобного нет в реальности.
VB, который мы знаем, зародился в отделе, который занимался разработкой Excel.
В момент своего, так сказать, рождения он назывался Excel Basic (сокращённо EB).500-страничную спефикацию этого будущего EB написал ни кто иной как Джоэль Спольски, о чём он рассказывает в своём блоге. Как ни странно, продукт под названием Visual Basic 1.0 к тому моменту уже существовал. Но это был по сути тот же досовский QuickBASIC с приделанной возможностью рисовать окошечки с помощью псевдографики:
Изображение

И вот команда разработки Excel связалась с командой разработки того VB 1.0 с инициативой, что команде Excel нужен «Бейсик для Excel». Об этом пишет Джоэль и рассказывает, что именно ему лично мы должны быть благодарны за то, что в современном VB есть 4 характерных штуки:
  • Переменные, которые могут хранить значения любого типа ( = вариант).
  • Позднее связывание ( = IDispatch)
  • Конструкция For Each, подсмотренная в csh.
  • Конструкция With, подсмотренная в Паскале.
Joel писал(а):The Excel team convinced the Basic team that what we really needed was some kind of Visual Basic for Excel. I managed to get four pet features added to Basic. I got them to add Variants, a union data type that could hold any other type, because otherwise you couldn’t store the contents of a spreadsheet cell in a variable without a switch statement. I got them to add late binding, which became known as IDispatch, a.k.a. COM Automation, because the original design for Silver required a deep understanding of type systems that the kinds of people who program macros don’t care about. And I got two pet syntactic features into the language: For Each, stolen from csh, and With, stolen from Pascal.

Тогда этот будущий объектно-ориентированный язык со всеми фишками, присущими современному языку, назывался Excel Basic (EB):
Joel писал(а):Then I sat down to write the Excel Basic spec, a huge document that grew to hundreds of pages. I think it was 500 pages by the time it was done.

Именно оттуда растут ноги у префикса «Eb» в функциях EbLoadRuntime, EbExecuteLine, EbInitHost, EbDoIdle, EbMode и куче других, которые экспортируются MSVBVM60.DLL и VBA6.DLL.

Потом рабочее название «Excel Basic» поменяли на «Visual Basic for Application» («со всеми этими ™ и ®):
Joel писал(а):Oh well. The party has moved elsewhere. Excel Basic became Microsoft Visual Basic for Applications for Microsoft Excel, with so many (TM)’s and (R)’s I don’t know where to put them all.

VBA не был основан на VC, это была совершенно самостоятельная среда со своей виртуальной машиной, со своим псевдокодом, которая не умела производить EXE и компилировать в Native-код.

И потом уже на фоне этого VBA и изначальной идее (известной внутри MS как «Ruby» — идея визуального проектирования интерфейса и его неразрывной связи этого интерфейса с неким кодом) был создан VB, который уже умел делать EXE.

Но опять таки, откуда повод говорить, что VB это доделанный Си? VB это доделанный VBA. А VBA это не доделанный Си.

VB6.EXE слинкован их скомпилированных исходников из папки ruby.
VBA6.DLL слинкован их скомпилированных исходников из папки vbadev.

MSVBVM60.DLL слинкован из смеси obj-файлов, полученных компилированием исходников из папок ruby и vbadev (я даже могу рассказать, что за что отвечает).

DLL-шки, которые используются Вордом, Экселем, Аксесом для обеспечения работы VBA скомпилированы из всё той же папки vbadev.

Все эти вещи скомпилированы из одних и тех же исходников, и по сути являются слиянием двух проектов: Ruby + Excel Basic. (Не путать концепцию Ruby Алана Купера с языком Ruby!).

Идёт ещё рядом с VB6 линкер: но линкер не является частью Visual C++. Линкер языконезависим, он слинкует всё, что ему подсунут, на каком бы языке оно ни было написано изначально.

И есть ещё рядом с VB6 некий C2.EXE, который занимается конвертацией промежуточного кода (IL) в Native-код. Этот компонентик VB6 с самой большой натяжкой может называться «отпрыском Visual C++».

Дело в том, что компилятор MSVC — вещь модульная. Она состоит из фронтенда и бэканда. Фронтенд занимается трансляцией входного кода (в виде текста) в некоторый промужуточный языко-независимый код. А бэкенд занимается трансляцией языко-независимого IL в Native-код.

CL.EXE, который вызывают, чтобы скомпилировать сишный исходник, всего лишь дёргает сначала фронтенд, а затем бэкенд. Причём для каждого языка в этой модульной архитектуре существует свой фронтенд.

Для Си без плюсов используется фронтенд «C1.DLL».
Для Си++ используется фроентенд «C1XX.DLL».
Результат работы обоих фронтендов транслируется в x86-машинный код бэкендом «C2.DLL».
Стыкует между собой фронтенд и бэкенд исполняемый файл «CL.EXE».
msvc_cl_modules.png
msvc_cl_modules.png (6.25 Кб) Просмотров: 10156

Будь у MS желание, они бы вдобавок к C1.DLL и C1XX.DLL могли бы написать ещё и P1.DLL, L1.DLL и хз1.DLL, и тогда CL мог бы компилировать вдобавок ещё и Паскаль, Лисп и ХЗ что ещё.

Мораль и вывод отсюда: C2.DLL (то есть бэкенд) к Си/Си++ привязан мало. Для языков специфичны фроентенды, а для машинных архитектур — бэкенды. (Кстати, у CL есть недокументированные ключи, которые позволяют подменить фроентенд и бэкенд своей собственной DLL-шкой).

Так вот, что сделали MS в отношении VB: они взяли бэкенд C2.DLL, который, напоминаю, не привязан к языку C/C++, обернули его в самостоятельный исполняемый модуль (C2.EXE) и включили его в состав VB как продукта.

Мало того, что этот x86-бэкенд не специфичен (а абстрагирован) от конкретно языков C/C++, так он ещё и используется VB только тогда, когда проект компилируется в Native-код, а при компиляции в P-код он вообще не использует.

Из всего, что это, это наибольшее, что связывает VB6 с Microsoft Visual C++. И после этого кто-то говорит, что VB это слегка допиленный Visual C++?

Дальнейшие пассажи с перечислением неких авторитетов и упоминанием некий JIT-компиляторов Си и ассемблера я вообще не понял, к чему тут.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

The trick
Постоялец
Постоялец
 
Сообщения: 781
Зарегистрирован: 26.06.2010 (Сб) 23:08

Re: Странное поведение ф-ции Timer

Сообщение The trick » 12.03.2017 (Вс) 11:48

TheWatcher писал(а):Тем досаднее вот эта вот телетайпная "бабочка" насчет "вообще", самонадеянно вырвавшаяся на волю так поспешно...

Речь шла о возвращаемом значении. Ты написал откровенную ересь:
TheWatcher писал(а):Если Currency (правда, нет такого типа в ИСО Си), то вся 80-битная ST0-ячейка FPU будет считаться валидной.



TheWatcher писал(а):Так вот, потрудитесь набросать и скомпилить как-то что-нить простенькое, типа

Интересно, а что ты ожидал? Во первых возвращаемое значение ни разу не в стеке, во-вторых ты сам явно делаешь конвертацию из Double в Currency и хочешь чтобы не использовался FPU стек?
Корректный тест:
Код: Выделить всё
Public Function Profit() As Currency
    Dim dollar As Currency
    dollar = 100@ ' cents
    Profit = dollar
End Function

Компилируется в:
Код: Выделить всё
CPU Disasm
Address                 Hex dump          Command                                  Comments
00401A50                /$  B8 40420F00   MOV EAX,0F4240                           ; Project1.00401A50(guessed void)
00401A55                |.  33D2          XOR EDX,EDX
00401A57                \.  C3            RETN

Как видишь возвращаемое значение возвращается в паре EDX:EAX.
UA6527P

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16478
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: Странное поведение ф-ции Timer

Сообщение Хакер » 12.03.2017 (Вс) 14:27

Ещё немного исследований.

Я взял вот такой код:
Код: Выделить всё
extern float rtcGetTimer();

float GetFdiff1()
{
    return rtcGetTimer() - rtcGetTimer();
}

float DoSum(double x, double y)
{
    return x+y;
}


И решил посмотреть, как обстоят дела с округлением у разных компиляторов Си. Кто по мнению компилятора отвечает за огрубление точности чрезмерно точных FP-чисел: вызывающая сторона или вызываемая.

MSVC
Компилируем:
cl -O1 -c -Fotest.obj test.c
Смотрим, какой код сгенерировался:
dumpbin -disasm test.obj

Получаем такой вывод:
Код: Выделить всё
_GetFdiff1:
  00000000: 55                 push        ebp
  00000001: 8B EC              mov         ebp,esp
  00000003: 51                 push        ecx
  00000004: E8 00 00 00 00     call        00000009
  00000009: D9 5D FC           fstp        dword ptr [ebp-4]
  0000000C: E8 00 00 00 00     call        00000011
  00000011: D8 6D FC           fsubr       dword ptr [ebp-4]
  00000014: C9                 leave
  00000015: C3                 ret

Код: Выделить всё
_DoSum:
  00000000: DD 44 24 04        fld         qword ptr [esp+4]
  00000004: DC 44 24 0C        fadd        qword ptr [esp+0Ch]
  00000008: C3                 ret


Округления нет ни на вызывающей стороне, ни в вызываемой функции!


GCC
Компилируем:
gcc -O4 -c -otest.obj test.c
Смотрим, какой код сгенерировался:
objdump -M intel --disassemble test.obj

Получаем такой вывод:
Код: Выделить всё
00000000 <GetFdiff1>:
   0:   83 ec 1c                sub    esp,0x1c
   3:   e8 fc ff ff ff          call   4 <GetFdiff1+0x4>
   8:   d9 5c 24 0c             fstp   DWORD PTR [esp+0xc]
   c:   e8 fc ff ff ff          call   d <GetFdiff1+0xd>
  11:   d8 6c 24 0c             fsubr  DWORD PTR [esp+0xc]
  15:   83 c4 1c                add    esp,0x1c
  18:   c3                      ret

Код: Выделить всё
00000020 <DoSum>:
  20:   83 ec 0c                sub    esp,0xc
  23:   dd 44 24 18             fld    QWORD PTR [esp+0x18]
  27:   dc 44 24 10             fadd   QWORD PTR [esp+0x10]
  2b:   d9 5c 24 04             fstp   DWORD PTR [esp+0x4]
  2f:   d9 44 24 04             fld    DWORD PTR [esp+0x4]
  33:   83 c4 0c                add    esp,0xc
  36:   c3                      ret


Округления на вызыващей стороне нет, а вот на вызываемой — есть (пара fstp DWORD PTR [esp+0x4] + fld DWORD PTR [esp+0x4])


У кого-нибудь есть желание другими Си-компиляторами проверить? В том числе CL из самых последних версий студии?
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

TheWatcher
Новичок
Новичок
Аватара пользователя
 
Сообщения: 29
Зарегистрирован: 27.08.2012 (Пн) 0:53
Откуда: Республика Беларусь

Re: Странное поведение ф-ции Timer

Сообщение TheWatcher » 13.03.2017 (Пн) 20:31

The trick писал(а):Корректный тест:
Код: Выделить всё
Public Function Profit() As Currency
    Dim dollar As Currency
    dollar = 100@ ' cents
    Profit = dollar
End Function

Компилируется в:
Код: Выделить всё
CPU Disasm
Address                 Hex dump          Command                                  Comments
00401A50                /$  B8 40420F00   MOV EAX,0F4240                           ; Project1.00401A50(guessed void)
00401A55                |.  33D2          XOR EDX,EDX
00401A57                \.  C3            RETN

Как видишь возвращаемое значение возвращается в паре EDX:EAX.


Я никак, ни в одном режиме оптимизации или без нее, не могу получить бинарник, который бы XORил EDX. Мало того, что в любых моих листингах на порядок больше строк, жонглирующих переменными и регистрами в стеке (обычном и FPU в зависимости от обрабатываемых значений), так еще и EDX, и память по нулю ANDятся, а не XORятся. Ты вообще откуда этот листинг взял? По диапазону адресов вроде на правду похоже... А по содержимому — так вроде и нет.
TheWatcher
=========
3.6GHz Core i5-3470, 16GB RAM / GTX 1060, 6GB VRAM
x86 Win XP Pro Sp3 / x64 Win 7 Ult Sp1 / x64 Ubuntu 16.04

The trick
Постоялец
Постоялец
 
Сообщения: 781
Зарегистрирован: 26.06.2010 (Сб) 23:08

Re: Странное поведение ф-ции Timer

Сообщение The trick » 13.03.2017 (Пн) 21:52

TheWatcher писал(а):Я никак, ни в одном режиме оптимизации или без нее, не могу получить бинарник, который бы XORил EDX.

Скинь тот скомпилированный код что я привел ранее.
TheWatcher писал(а):Мало того, что в любых моих листингах на порядок больше строк, жонглирующих переменными и регистрами в стеке (обычном и FPU в зависимости от обрабатываемых значений), так еще и EDX, и память по нулю ANDятся, а не XORятся.

Не знаю что ты там компилируешь. Возвращение в паре EDX;EAX это обычная практика 64 битных возвращаемых значений.
TheWatcher писал(а):Ты вообще откуда этот листинг взял?

Это листинг из скомпилированного приведенного мною кода. Просто вызываешь функцию в нужном месте, к примеру в Form_load или Sub Main.
UA6527P


Вернуться в Visual Basic 1–6

Кто сейчас на конференции

Сейчас этот форум просматривают: AhrefsBot и гости: 55

    TopList  
cron