DELPHI: Как избежать недостатка Eintoverflow при вычитании?
-
19-09-2019 - |
Вопрос
Microsoft уже говорит, в документации для GetTickCount, что вы никогда не сможете сравнить количество клещей, чтобы проверить, прошел ли интервал. например:
Неправильно (псевдокод):
DWORD endTime = GetTickCount + 10000; //10 s from now
...
if (GetTickCount > endTime)
break;
Приведенный выше код плохой, потому что он является абонентом для переноса счетчика тика. Например, предположим, что часы находятся ближе к концу его диапазона:
endTime = 0xfffffe00 + 10000
= 0x00002510; //9,488 decimal
Затем вы выполняете свой чек:
if (GetTickCount > endTime)
Что сразу удовлетворяется, поскольку GetTickCount
является больше, чем endTime
:
if (0xfffffe01 > 0x00002510)
Решение
Вместо этого вы всегда должны вычитать два временных интервала:
DWORD startTime = GetTickCount;
...
if (GetTickCount - startTime) > 10000 //if it's been 10 seconds
break;
Глядя на ту же математику:
if (GetTickCount - startTime) > 10000
if (0xfffffe01 - 0xfffffe00) > 10000
if (1 > 10000)
Что все хорошо и хорошо в C/C ++, где компилятор ведет себя определенным образом.
Но как насчет Дельфи?
Но когда я выполняю ту же математику в Delphi, с переполнением (переполнение ({Q+}
, {$OVERFLOWCHECKS ON}
), вычитание из двух клеток генерирует исключение Eintoverflow, когда Tickcount переворачивается:
if (0x00000100 - 0xffffff00) > 10000
0x00000100 - 0xffffff00 = 0x00000200
Какое предполагаемое решение для этой проблемы?
РЕДАКТИРОВАТЬ: Я пытался временно выключить OVERFLOWCHECKS
:
{$OVERFLOWCHECKS OFF}]
delta = GetTickCount - startTime;
{$OVERFLOWCHECKS ON}
Но вычитание все еще бросает EIntOverflow
исключение.
Есть ли лучшее решение с участием литков и больших типов промежуточных переменных?
Обновлять
Еще один вопрос, который я задал, объяснил, почему {$OVERFLOWCHECKS}
не работает. Это, очевидно, работает только в функция Уровень, а не линия уровень. Итак, пока следующие не Работа:
{$OVERFLOWCHECKS OFF}]
delta = GetTickCount - startTime;
{$OVERFLOWCHECKS ON}
следующее делает Работа:
delta := Subtract(GetTickCount, startTime);
{$OVERFLOWCHECKS OFF}]
function Subtract(const B, A: DWORD): DWORD;
begin
Result := (B - A);
end;
{$OVERFLOWCHECKS ON}
Решение
Как насчет такой простой функции, как эта?
function GetElapsedTime(LastTick : Cardinal) : Cardinal;
var CurrentTick : Cardinal;
begin
CurrentTick := GetTickCount;
if CurrentTick >= LastTick then
Result := CurrentTick - LastTick
else
Result := (High(Cardinal) - LastTick) + CurrentTick;
end;
Так что у тебя есть
StartTime := GetTickCount
...
if GetElapsedTime(StartTime) > 10000 then
...
Он будет работать до тех пор, пока время между началом времени и текущим GetTickCount меньше, чем печально известный 49,7 -дневный диапазон GetTickCount.
Другие советы
Я перестал делать эти расчеты повсюду после написания нескольких вспомогательных функций, которые называются вместо этого.
Использовать новый GetTickCount64()
Функция на Vista, а затем есть следующий новый тип:
type
TSystemTicks = type int64;
который используется для всех таких расчетов. GetTickCount()
никогда не называется непосредственно, вспомогательная функция GetSystemTicks()
вместо этого используется:
type
TGetTickCount64 = function: int64; stdcall;
var
pGetTickCount64: TGetTickCount64;
procedure LoadGetTickCount64;
var
DllHandle: HMODULE;
begin
DllHandle := LoadLibrary('kernel32.dll');
if DllHandle <> 0 then
pGetTickCount64 := GetProcAddress(DllHandle, 'GetTickCount64');
end;
function GetSystemTicks: TSystemTicks;
begin
if Assigned(pGetTickCount64) then
Result := pGetTickCount64
else
Result := GetTickCount;
end;
// ...
initialization
LoadGetTickCount64;
end.
Вы могли бы даже вручную отслеживать оберток GetTickCount()
Возвращающаяся стоимость и вернуть истинное 64 -битное системное количество также на более ранние системы, которые должны работать довольно хорошо, если вы позвоните GetSystemTicks()
функционируйте, по крайней мере, каждые несколько дней. [Кажется, я где -то помню реализацию этого, но не помню, где это было. Габр опубликовал ссылка и реализация.]
Теперь тривиально реализовать такие функции, как
function GetTicksRemaining(...): TSystemTicks;
function GetElapsedTicks(...): TSystemTicks;
function IsTimeRunning(...): boolean;
это скрывает детали. Призыв этих функций вместо расчета продолжительности на месте также служит документацией намерения кода, поэтому необходимо меньше комментариев.
Редактировать:
Вы пишете в комментарии:
Но, как вы сказали, запасная сторона Windows 2000 и XP, чтобы GetTickCount по -прежнему оставляет исходную проблему.
Вы можете исправить это легко. Сначала нет необходимость чтобы вернуться к GetTickCount()
- Вы можете использовать код, предоставленный GABR Рассчитайте 64 -битный счетчик по старым системам также. (Вы можете заменить timeGetTime()
с GetTickCount)
Если хочешь.)
Но если вы не хотите этого делать, вы можете так же хорошо отключить диапазон и переполнять проверки в функциях помощников или проверить, меньше ли Minuend, чем подтрахни и коррет для этого, добавив $ 100000000 (2^32), чтобы моделировать 64 -битное количество клеток. Или реализовать функции в ассемблере, и в этом случае код не имеет чеков (не то, что я бы посоветовал это, но это возможно).
Вы также можете использовать dsitimegettime64 из DSIWIN32:
threadvar
GLastTimeGetTime: DWORD;
GTimeGetTimeBase: int64;
function DSiTimeGetTime64: int64;
begin
Result := timeGetTime;
if Result < GLastTimeGetTime then
GTimeGetTimeBase := GTimeGetTimeBase + $100000000;
GLastTimeGetTime := Result;
Result := Result + GTimeGetTimeBase;
end; { DSiTimeGetTime64 }
Вы можете использовать DataType Int64, чтобы избежать переполнения:
var
Start, Delta : Int64;
begin
Start := GetTickCount;
...
Delta := GetTickCount - start;
if (Delta > 10000) then
...