如果一个帖子正在做这样的事情:
const DWORD interval = 20000;
DWORD ticks = GetTickCount();
while(true)
{
DoTasksThatTakeVariableTime();
if( GetTickCount() - ticks > interval )
{
DoIntervalTasks();
ticks = GetTickCount();
}
}
最终,当值不适合DWORD时,ticks将会换行。
我和同事一直在讨论这个问题。我们中的一个人认为,当发生换行时,代码仍会表现得很“漂亮”,因为减法操作也会换行。我们其他人认为它并不总是有效,特别是如果间隔很大。
谁是对的,为什么?
感谢。
答案 0 :(得分:13)
来自the docs:
经过的时间存储为DWORD 值。因此,时间会结束 如果系统运行,则大约为零 连续49.7天。避免 这个问题,请使用GetTickCount64。 否则,检查溢出 比较时间的条件。
然而,DWORD没有签名 - 所以你应该没问题。 0 - “非常大的数字”=“小数字”(假设您没有激活溢出检查,当然)。我之前有一个编辑建议你得到一个负数,但那是在我考虑到DWORD未签名之前。
如果操作在 49.7天之后仍然存在问题,那么你仍会遇到问题。这对你来说可能不是问题;)
测试的一种方法是删除GetTickCount()
方法,这样你就可以在明确地将其包装的地方编写单元测试。再说一次,如果你真的只是怀疑算术部分,你可以很容易地为它编写单元测试:)真的,这个数字来自系统时钟的事实几乎是不相关的,只要你知道它的行为。包装 - 并且在文档中指定。
答案 1 :(得分:10)
只要:
,没有什么不好的事情发生您减去DWORD
,而不是先转换为其他类型。
你想要的时间不会超过49.7天。
这是因为无符号算术溢出在C中定义良好,包装行为完全符合我们的要求。
DWORD t1, t2;
DWORD difference;
t1 = GetTickCount();
DoSomethingTimeConsuming();
t2 = GetTickCount();
t2 - t1
将生成正确的值,即使GetTickCount
包围。在进行减法操作之前,请勿将t2
和t1
转换为其他类型(例如int
或double
)。
如果编程语言将溢出视为错误,则无效。如果DoSomethingTimeConsuming()
的时间超过49.7天,它也将无效。不幸的是,您只能通过查看t2
和t1
GetTickCount
缠绕的次数来判断。
让我们从通常情况开始,没有发生回旋:
t1 = 13487231
t2 = 13492843
这里,t2 - t1 = 5612
,这意味着操作大约需要五秒钟。
现在考虑一项花费很短时间的操作,但GetTickCount
确实包含了这些操作:
t1 = 4294967173
t2 = 1111
操作耗时1234毫秒,但计时器缠绕,1111 - 4294967173
是-4294966062
的虚假值。我们将做什么?
嗯,模2 32 ,减法的结果也包围了:
(DWORD)-4294966062 == (DWORD)1234
最后,考虑边缘情况,其中操作接近 2 32 毫秒,但不完全:
t1 = 2339189280
t2 = 2339167207
在这里,GetTickCount
缠绕着,然后回到原来的位置。
现在t2 - t1
会产生看似虚假的4294945223
值。那是因为这是操作实际花费的时间!
一般来说:
(base + offset) - base ≡ offset mod 2^32
答案 2 :(得分:7)
如果要测试GetTickCount()
换行时会发生什么,可以启用Application Verifier的TimeRollOver测试。
来自Using Application Verifier Within Your Software Development Lifecycle:
TimeRollOver 强制GetTickCount和TimeGetTime API比平时更快地翻转。这使应用程序可以更轻松地测试它们对时间翻转的处理。
答案 3 :(得分:4)
我建议计算两个滴答之间的实际经过时间,而不是依靠编译器为你处理它:
const DWORD interval = 20000;
#define TICKS_DIFF(prev, cur) ((cur) >= (prev)) ? ((cur)-(prev)) : ((0xFFFFFFFF-(prev))+1+(cur))
DWORD ticks = GetTickCount();
while(true)
{
DoTasksThatTakeVariableTime();
DWORD curticks = GetTickCount();
if( TICKS_DIFF(ticks, curticks) > interval )
{
DoIntervalTasks();
ticks = GetTickCount();
}
}
答案 4 :(得分:2)
我最近遇到了这个问题。我正在处理的代码在一堆地方使用GetTickCount()来确定程序是否在特定任务上花费了太多时间,如果是这样,它将超时该任务并重新安排它以便以后执行。会发生的是,如果GetTickCount()在其中一个测量周期中换行,则会导致代码过早超时。这是一项不断运行的服务,因此每49天就会有一次轻微的打嗝。
我通过编写一个在内部使用GetTickCount()的计时器类来修复它,但是当值包装并对其进行补偿时检测到它。
答案 5 :(得分:1)
当然,您需要处理此剔除问题。
Linux内核使用以下技巧处理此类剔除问题:
#define time_after(a,b)((long)(b) - (long)(a)< 0))
这个想法是无符号转换为签名并比较它们的值,那么只有当| a-b |< 2 ^ 30,然后换行不会影响结果。
你可以试试这个技巧,并了解其工作原理。
由于DWORD也是unsigned int,因此这个技巧也适用于windows。
所以你的代码可能就像:
const DWORD interval = 20000;
DWORD ticks = GetTickCount()+ interval;
while(true){
DoTasksThatTakeVariableTime(); if(time_after(ticks, GetTickCount()) { DoIntervalTasks(); ticks = GetTickCount() + interval; }
}
仅当间隔小于0x2 ^ 30时才有效。
答案 6 :(得分:1)
你可以测试它;) - 我在这里有一个简单的测试应用程序,它将启动一个应用程序并在其中挂钩GetTickCount()
,以便您可以从测试应用程序的GUI控制它。我把它写成一些应用程序中的GetTickCount()
调用并不容易。
TickShifter是免费的,可在此处获取:http://www.lenholgate.com/blog/2006/04/tickshifter-v02.html
我在撰写一系列关于测试驱动开发的文章时写了这篇文章,该文章使用了一些以破解方式使用GetTickCount()
的代码。
如果您有兴趣,请在此处提供文章:http://www.lenholgate.com/blog/2004/05/practical-testing.html
但是,总而言之,您的代码将有效......
答案 7 :(得分:1)
答案 8 :(得分:1)
这篇文章帮助了我,但我认为有一个错误:
#define TICKS_DIFF(prev, cur) ((cur) >= (prev)) ? ((cur)-(prev)) : ((0xFFFFFFFF-(prev))+(cur))
当我在环绕点测试时,我发现它已经被1点关闭了。
对我有用的是:
define TICKS_DIFF(prev, cur) ((cur) >= (prev)) ? ((cur)-(prev)) : ((0xFFFFFFFF-(prev))+(cur)+1)
答案 9 :(得分:1)
我知道这几乎是不相关的,将近11年之后,并且从Vista开始就包含了GetTickCount64(),但是这是我从那时起一直在我的工具中使用的一些帮助程序代码,我曾经经常使用它这是一个问题。
inline DWORD GetElapsed(DWORD from, DWORD to = ::GetTickCount())
{
if (from < to) //check for wrap around condition
return (to - from);
else
return ((0xFFFFFFFFL - from) + 1 + to);
}
用法
DWORD start = ::GetTickCount();
// Some time later
DWORD elapsed = GetElapsed(start);
担心的不是您的操作可能不会花费更长的49.7天,而是滴答计数器可能会在您的操作过程中翻转,从而使经过时间的计算变得不可靠。
当然,由于GetTickCount64(),现在所有都不相关