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
    ...