微软已经在 GetTickCount 的文档中表示,您永远无法通过比较滴答计数来检查间隔是否已过去。例如:
不正确(伪代码):
DWORD endTime = GetTickCount + 10000; //10 s from now
...
if (GetTickCount > endTime)
break;
上面的代码很糟糕,因为它很容易发生滴答计数器的翻转。例如,假设时钟接近其范围的末尾:
endTime = 0xfffffe00 + 10000
= 0x00002510; //9,488 decimal
然后执行检查:
if (GetTickCount > endTime)
立即满足,因为 GetTickCount
is 大于 endTime< /code>:
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}
Microsoft already says, in the documentation for GetTickCount, that you could never compare tick counts to check if an interval has passed. e.g.:
Incorrect (pseudo-code):
DWORD endTime = GetTickCount + 10000; //10 s from now
...
if (GetTickCount > endTime)
break;
The above code is bad because it is suceptable to rollover of the tick counter. For example, assume that the clock is near the end of it's range:
endTime = 0xfffffe00 + 10000
= 0x00002510; //9,488 decimal
Then you perform your check:
if (GetTickCount > endTime)
Which is satisfied immediatly, since GetTickCount
is larger than endTime
:
if (0xfffffe01 > 0x00002510)
The solution
Instead you should always subtract the two time intervals:
DWORD startTime = GetTickCount;
...
if (GetTickCount - startTime) > 10000 //if it's been 10 seconds
break;
Looking at the same math:
if (GetTickCount - startTime) > 10000
if (0xfffffe01 - 0xfffffe00) > 10000
if (1 > 10000)
Which is all well and good in C/C++, where the compiler behaves a certain way.
But what about Delphi?
But when i perform the same math in Delphi, with overflow checking on ({Q+}
, {$OVERFLOWCHECKS ON}
), the subtraction of the two tick counts generates an EIntOverflow exception when the TickCount rolls over:
if (0x00000100 - 0xffffff00) > 10000
0x00000100 - 0xffffff00 = 0x00000200
What is the intended solution for this problem?
Edit: i've tried to temporarily turn off OVERFLOWCHECKS
:
{$OVERFLOWCHECKS OFF}]
delta = GetTickCount - startTime;
{$OVERFLOWCHECKS ON}
But the subtraction still throws an EIntOverflow
exception.
Is there a better solution, involving casts and larger intermediate variable types?
Update
Another SO question i asked explained why {$OVERFLOWCHECKS}
doesn't work. It apparently only works at the function level, not the line level. So while the following doesn't work:
{$OVERFLOWCHECKS OFF}]
delta = GetTickCount - startTime;
{$OVERFLOWCHECKS ON}
the following does work:
delta := Subtract(GetTickCount, startTime);
{$OVERFLOWCHECKS OFF}]
function Subtract(const B, A: DWORD): DWORD;
begin
Result := (B - A);
end;
{$OVERFLOWCHECKS ON}
发布评论
评论(4)
像这样的简单函数怎么样?
因此,
只要 StartTime 和当前 GetTickCount 之间的时间小于臭名昭著的 GetTickCount 的 49.7 天范围,它就可以工作。
How about a simple function like this one?
So you have
It will work as long as the time between StartTime and the current GetTickCount is less than the infamous 49.7 days range of GetTickCount.
在编写了一些被调用的辅助函数之后,我已经停止在任何地方进行这些计算。
使用新的
GetTickCount64()
函数有以下新类型:
用于所有此类计算。
GetTickCount()
永远不会直接调用,而是使用辅助函数GetSystemTicks()
:您甚至可以手动跟踪
GetTickCount()< /code> 返回值并在早期系统上返回真正的 64 位系统滴答计数,如果您至少每隔几天调用
GetSystemTicks()
函数,那么它应该可以很好地工作。 [我似乎记得某处的实现,但不记得它在哪里。gabr 发布了 链接和实现。]现在实现这样的函数很简单,
它将隐藏细节。调用这些函数而不是就地计算持续时间也可以作为代码意图的文档,因此需要更少的注释。
编辑:
您在评论中写道:
您可以轻松解决此问题。首先,您不需要需要回退到
GetTickCount()
- 您可以使用提供给在旧系统上也计算 64 位滴答计数。 (如果需要,您可以将timeGetTime()
替换为GetTickCount)
。)但是如果您不想这样做,也可以禁用范围和溢出检查辅助函数,或者检查被减数是否小于减数,并通过添加 $100000000 (2^32) 来模拟 64 位刻度计数来进行纠正。或者在汇编程序中实现函数,在这种情况下,代码没有检查(我不建议这样做,但这是一种可能性)。
I have stopped doing these calculations everywhere after writing a few helper functions that are called instead.
To use the new
GetTickCount64()
function on Vista and later there is the following new type:which is used for all such calculations.
GetTickCount()
is never called directly, the helper functionGetSystemTicks()
is used instead:You could even manually track the wrap-around of the
GetTickCount()
return value and return a true 64 bit system tick count on earlier systems too, which should work fairly well if you call theGetSystemTicks()
function at least every few days. [I seem to remember an implementation of that somewhere, but don't remember where it was.gabr posted a link and the implementation.]Now it's trivial to implement functions like
that will hide the details. Calling these functions instead of calculating durations in-place serves also as documentation of the code intent, so less comments are necessary.
Edit:
You write in a comment:
You can fix this easily. First you don't need to fall back to
GetTickCount()
- you can use the code gabr provided to calculate a 64 bit tick count on older systems as well. (You can replacetimeGetTime()
withGetTickCount)
if you want.)But if you don't want to do that you can just as well disable range and overflow checks in the helper functions, or check whether the minuend is smaller than the subtrahend and correct for that by adding $100000000 (2^32) to simulate a 64 bit tick count. Or implement the functions in assembler, in which case the code doesn't have the checks (not that I would advise this, but it's a possibility).
您还可以使用 DSiWin32 中的 DSiTimeGetTime64:
You can also use DSiTimeGetTime64 from the DSiWin32:
您可以使用 Int64 数据类型来避免溢出:
You can use the Int64 datatype to avoid overflow: