10

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)

ソリューション

代わりに、常に 2 つの時間間隔を減算する必要があります。

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+}, ) のオーバーフロー チェックを使用して Delphi で同じ計算を実行すると、 TickCountがロールオーバー{$OVERFLOWCHECKS ON}すると、2 つのティック カウントの減算により EIntOverflow 例外が生成されます。

if (0x00000100 - 0xffffff00) > 10000

0x00000100 - 0xffffff00 = 0x00000200

この問題の意図した解決策は何ですか?

編集:一時的にオフにしようとしましたOVERFLOWCHECKS

{$OVERFLOWCHECKS OFF}]
   delta = GetTickCount - startTime;
{$OVERFLOWCHECKS ON}

しかし、減算は依然としてEIntOverflow例外をスローします。

キャストとより大きな中間変数型を含む、より良い解決策はありますか?


アップデート

私が尋ねた別のSOの質問では、なぜ{$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}
4

4 に答える 4

6

このような単純な関数はどうですか?

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 日の範囲よりも短い限り、これは機能します。

于 2010-03-17T15:08:51.203 に答える
5

代わりに呼び出されるいくつかのヘルパー関数を作成した後、私はこれらの計算をどこでも行うのをやめました。

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.

戻り値のラップアラウンドを手動で追跡し、以前のシステムでも真の 64 ビット システム ティック カウントを返すこともできます。これは、少なくとも数日おきGetTickCount()に関数を呼び出すと、かなりうまく機能するはずです。GetSystemTicks()[どこかでその実装を覚えているようですが、どこにあったか覚えていません。gabrがリンクと実装を投稿しました。]

次のような関数を実装するのは簡単です

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

それは詳細を隠します。インプレースで期間を計算する代わりにこれらの関数を呼び出すことは、コードの意図のドキュメントとしても機能するため、必要なコメントは少なくなります。

編集:

コメントに次のように書きます。

しかし、あなたが言ったように、Windows 2000 と XP での GetTickCount へのフォールバックは、依然として元の問題を残しています。

これは簡単に修正できます。まず、フォールバックする必要はありませんGetTickCount()。提供されているコード gabr を使用して、古いシステムでも 64 ビットのティック カウントを計算できます。timeGetTime()(お好みで差し替えも可能GetTickCount)です。)

しかし、それをしたくない場合は、ヘルパー関数で範囲とオーバーフローのチェックを無効にするか、被減数が減数よりも小さいかどうかを確認し、$100000000 (2^32) を追加してシミュレートすることで修正できます。 64 ビットのティック カウント。または、アセンブラーで関数を実装します。その場合、コードにはチェックがありません (私がこれを勧めるわけではありませんが、可能性はあります)。

于 2010-03-10T15:41:54.723 に答える
3

DSiWin32 からDSiTimeGetTime64を使用することもできます。

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 }
于 2010-03-10T16:50:43.680 に答える
1

Int64 データ型を使用して、オーバーフローを回避できます。

var
  Start, Delta : Int64;
begin
  Start := GetTickCount;
  ...
  Delta := GetTickCount - start;
  if (Delta > 10000) then
    ...
于 2010-03-10T15:31:40.440 に答える