Delphi: Как избежать, чтобы EIntOverflow потеряли значимость при вычитании?

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)

Который удовлетворен immediatly с тех пор 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?

Но когда я выполняю ту же математику в 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}
10
задан Ian Boyd 17 March 2010 в 19:23
поделиться

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 меньше печально известного диапазона GetTickCount в 49,7 дней.

6
ответ дан 3 December 2019 в 22:36
поделиться

Вы также можете использовать 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 }
3
ответ дан 3 December 2019 в 22:36
поделиться

Можно использовать тип данных Int64, чтобы избежать переполнения:

var
  Start, Delta : Int64;
begin
  Start := GetTickCount;
  ...
  Delta := GetTickCount - start;
  if (Delta > 10000) then
    ...
1
ответ дан 3 December 2019 в 22:36
поделиться

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

Чтобы использовать новую функцию 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 () хотя бы раз в несколько дней. [ Кажется, я где-то помню реализацию этого, но не помню, где это было. gabr опубликовал ссылку и реализацию .]

Теперь тривиально реализовать такие функции, как

function GetTicksRemaining(...): TSystemTicks;
function GetElapsedTicks(...): TSystemTicks;
function IsTimeRunning(...): boolean;

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

Изменить:

Вы пишете в комментарии:

Но, как вы сказали, откат GetTickCount в Windows 2000 и XP по-прежнему оставляет исходную проблему.

Вы можете легко это исправить. Во-первых, вам не требуется для возврата к GetTickCount () - вы можете использовать код gabr, предоставленный для расчета 64-битного счетчика тиков в старых системах , поскольку хорошо. (Вы можете заменить timeGetTime () на GetTickCount) , если хотите.)

Но если вы не хотите этого делать, вы можете просто отключить проверки диапазона и переполнения во вспомогательных функциях или проверить, меньше ли minuend, чем вычитаемое, и исправить это, добавив 100000000 долларов (2 ^ 32 ) для имитации 64-битного счетчика тиков. Или реализовать функции на ассемблере, и в этом случае в коде нет проверок (не то чтобы я советовал это, но это возможно).

5
ответ дан 3 December 2019 в 22:36
поделиться
Другие вопросы по тегам:

Похожие вопросы: