DELPHI: Как избежать недостатка Eintoverflow при вычитании?

StackOverflow https://stackoverflow.com/questions/2417919

Вопрос

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
    ...
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top