C语言中GetTickCount如何精确获取运行时间?

99ANYc3cd6
预计阅读时长 14 分钟
位置: 首页 C语言 正文

GetTickCount() 是一个在 Windows 平台上广泛使用的 API 函数,它返回自 Windows 操作系统启动以来经过的毫秒数,它常用于程序中测量代码执行时间、实现简单的定时器或延时功能。

c语言gettickcount
(图片来源网络,侵删)

函数原型

GetTickCount() 的函数原型非常简单,定义在 <Windows.h> 头文件中:

DWORD GetTickCount(void);
  • 返回值类型: DWORD,这是一个 32 位无符号整数(unsigned long)。
  • 返回值: 自系统启动后经过的毫秒数。

工作原理与精度

GetTickCount() 的值是系统启动后不断累加的一个计数器,其计数频率与系统的定时器中断频率有关,通常是 10 毫秒或 15.6 毫秒更新一次,它的精度并不高,通常在 10-16 毫秒左右。

重要问题:32位整数溢出

DWORD 是一个 32 位无符号整数,它的最大值是 2^32 - 1,即 4,294,967,295

c语言gettickcount
(图片来源网络,侵删)
  • 如果系统以 15.625 毫秒/次的速度更新,那么大约 4,294,967,295 / 1000 / 60 / 60 / 24 ≈ 49.7 天后会溢出。
  • 如果系统以 10 毫秒/次的速度更新,那么大约 4,294,967,295 / 1000 / 60 / 60 / 24 ≈ 49.7 天后也会溢出。

溢出后会发生什么?

溢出后,GetTickCount() 的值会从 0xFFFFFFFF(约 49.7 天)回绕到 0,如果你的程序长时间运行(超过 49.7 天),或者你用它来测量一个跨越了溢出点的时长,直接进行减法运算会导致结果错误。

示例:错误的计算

// 假设系统已经运行了 50 天,GetTickCount() 已经回绕到 1000
DWORD start = 1000; 
// ... 程序运行了 10 分钟 (600,000 毫秒)
// 实际的TickCount应该是 1000 + 600000 = 601000
// 但由于溢出,实际返回的值可能是一个很小的数,500000
DWORD end = 500000; // 假设溢出后是这个值
// 错误的计算
DWORD elapsed = end - start; // 500000 - 1000 = 499000 (结果完全错误)

如何正确使用(避免溢出问题)

为了正确计算时间差,即使发生了溢出,我们需要一个特殊的技巧:将 DWORD 当作一个环形计数器来处理。

c语言gettickcount
(图片来源网络,侵删)

end 小于 start 时,意味着发生了溢出,正确的计算方法是:

elapsed = (MAX_DWORD - start) + end + 1

或者,利用 C 语言中无符号整数溢出会自动回绕的特性,可以直接用减法,结果也是正确的:

elapsed = end - start; (当 end 和 start 都是 DWORD 类型时)

正确计算时间差的函数示例:

#include <stdio.h>
#include <Windows.h>
// 计算两个 GetTickCount() 值之间经过的毫秒数(正确处理溢出)
DWORD GetElapsedMillis(DWORD start, DWORD end) {
    // 因为 DWORD 是无符号整数,当 end < start 时,end - start 会自动计算
    // 出 (MAX_DWORD - start) + end 的正确结果,无需手动判断。
    return end - start;
}
int main() {
    DWORD start, end, elapsed;
    start = GetTickCount();
    // 模拟一段耗时操作
    Sleep(500); // 暂停 500 毫秒
    end = GetTickCount();
    elapsed = GetElapsedMillis(start, end);
    printf("开始时间戳: %u\n", start);
    printf("结束时间戳: %u\n", end);
    printf("经过时间: %u 毫秒\n", elapsed);
    return 0;
}

优点与缺点

优点:

  1. 简单易用: 只需调用一个函数即可,非常方便。
  2. 开销小: 它是一个轻量级的系统调用,性能开销非常低。
  3. 兼容性好: 从古老的 Windows 95 到最新的 Windows 11 都支持。

缺点:

  1. 精度较低: 精度通常在 10-16 毫秒,对于需要高精度计时的场景(如游戏、物理模拟)不适用。
  2. 存在溢出问题: 如上所述,需要特殊处理才能跨越溢出点正确计算时间。

替代方案

如果你需要更高精度或希望避免 GetTickCount() 的溢出问题,可以使用以下更现代的 Windows API:

QueryPerformanceCounter (高精度计时器)

这是在 Windows 上进行性能计时的黄金标准

  • QueryPerformanceCounter(): 获取一个高分辨率的性能计数器的值,这个值本身没有特定的时间单位,只是一个数字。
  • QueryPerformanceFrequency(): 获取性能计数器的频率,即每秒多少次,这个频率是固定的,通常在 1MHz 到 50MHz 之间,非常高。

使用方法:

  1. 调用 QueryPerformanceFrequency() 获取频率 freq
  2. 在计时开始时调用 QueryPerformanceCounter() 获取起始计数 start_count
  3. 在计时结束时调用 QueryPerformanceCounter() 获取结束计数 end_count
  4. 计算经过的秒数:elapsed_seconds = (double)(end_count - start_count) / (double)freq

优点:

  • 精度极高,可以达到微秒(μs)甚至纳秒级别。
  • 不会溢出LARGE_INTEGER 类型(通常是 64 位)非常大,溢出需要极长的时间(数千年),可以忽略不计。

缺点:

  • 相对 GetTickCount() 稍微复杂一些。

GetTickCount64() (64位版本)

这是 GetTickCount() 的 64 位版本,直接解决了溢出问题。

  • 函数原型: ULONGLONG GetTickCount64(void);
  • 返回值: 一个 64 位无符号整数,表示自系统启动以来的毫秒数。

优点:

  • 解决了溢出问题:64 位的数值极大,溢出需要数万年,对于任何应用程序来说都足够了。
  • 使用方式与 GetTickCount() 完全相同,非常简单。

缺点:

  • 仅在 Windows Vista 及更高版本 中可用。

总结与建议

特性 GetTickCount() GetTickCount64() QueryPerformanceCounter()
精度 低 (~15ms) 低 (~15ms) 极高 (μs级)
返回值类型 DWORD (32位) ULONGLONG (64位) LARGE_INTEGER (64位)
溢出问题 (约49.7天) (数万年) (数千年)
系统要求 所有Windows版本 Windows Vista+ 所有Windows版本
使用复杂度 非常简单 非常简单 较复杂

如何选择?

  1. 追求极致简单,且程序运行时间远小于49.7天:可以使用 GetTickCount(),但必须记得使用无符号整数减法来处理时间差。
  2. 使用 Windows Vista 或更高版本,且希望简单避免溢出问题强烈推荐使用 GetTickCount64(),它是 GetTickCount() 的完美现代替代品。
  3. 需要高精度计时(游戏、基准测试等)必须使用 QueryPerformanceCounter(),它是性能测量的行业标准。
  4. 编写需要跨平台(如Linux/macOS)的代码GetTickCount() 是 Windows 特有的,在 Linux 上,你可以使用 clock_gettime();在 macOS 上,可以使用 clock_gettime()mach_absolute_time()QueryPerformanceCounter() 也是 Windows 特有的。
-- 展开阅读全文 --
头像
C语言SetWindowLong函数如何修改窗口属性?
« 上一篇 12-10
C语言中fillrectangle函数如何使用?
下一篇 » 12-10

相关文章

取消
微信二维码
支付宝二维码

目录[+]