Delphi: как избежать eintoverflow underflow при вычитании?
Microsoft уже говорит в документации для GetTickCount, что вы никогда не сможете сравнить количество тиков, чтобы проверить, прошел ли интервал. например:
неправильные (псевдо-код):
DWORD endTime = GetTickCount + 10000; //10 s from now
...
if (GetTickCount > endTime)
break;
приведенный выше код является плохим, потому что он является suceptable для опрокидывания тикового счетчика. Например, предположим, что часы находятся в конце диапазона:
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
какое решение этой проблемы?
Edit: я попытался временно отключить OVERFLOWCHECKS
:
{$OVERFLOWCHECKS OFF}]
delta = GetTickCount - startTime;
{$OVERFLOWCHECKS ON}
но вычитание по-прежнему выдает EIntOverflow
исключения.
есть ли лучшее решение, включающее слепки и большую промежуточную переменную типы?
обновление
еще один вопрос, который я задал, объяснил, почему {$OVERFLOWCHECKS}
не работает. По-видимому, он работает только в функции
4 ответов
Как насчет простой функции, как эта?
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
...
он будет работать до тех пор, пока время между StartTime и текущим 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()
вернуться значение и возвращает true 64-битный системный тиковый счетчик на более ранних системах, который должен работать довольно хорошо, если вы вызываете
вы также можете использовать DSiTimeGetTime64 из DSiWin32:
threadvar
GLastTimeGetTime: DWORD;
GTimeGetTimeBase: int64;
function DSiTimeGetTime64: int64;
begin
Result := timeGetTime;
if Result < GLastTimeGetTime then
GTimeGetTimeBase := GTimeGetTimeBase + 0000000;
GLastTimeGetTime := Result;
Result := Result + GTimeGetTimeBase;
end; { DSiTimeGetTime64 }
вы можете использовать тип данных Int64, чтобы избежать переполнения:
var
Start, Delta : Int64;
begin
Start := GetTickCount;
...
Delta := GetTickCount - start;
if (Delta > 10000) then
...