Windows C++ clock函数不随系统时间变化问题(升级至VS2015及之后版本)
最近项目升级到VS2017,无法通过调系统时间测试业务问题
1 问题分析
调试发现工程中很多定时器和时间相关业务都是通过 clock
函数实现,而 clock
微软在VS2015及之后版本做了重新实现:
- VS2015前,
clock
随系统时间变化 - VS2015及之后,
clock
不随系统时间变化
从Microsoft官方文档:Microsoft C/C++ change history 2003 - 2015,截取的描述:
<time.h>
clock
In previous versions, the clock function was implemented using the Windows API GetSystemTimeAsFileTime. With this implementation, the clock function was sensitive to the system time, and was thus not necessarily monotonic. The clock function has been reimplemented in terms of QueryPerformanceCounter and is now monotonic.
大致是说 clock
以前使用 GetSystemTimeAsFileTime
实现会对系统时间敏感,后续更改为使用 QueryPerformanceCounter
实现
这时去看了下官方的clock实现(题主只有VS2008和VS2017)
VS2008 clock实现:
代码在:代码在:<VS2008安装目录>/VC/crt/src/clock.c
VS2017 clock实现:
代码在:<sdk目录>/ucrt/time/clock.cpp
通过对比,可以看出和官方文档描述的一样,clock
用 QueryPerformanceCounter
重新实现了
2 解决方案
自定义与 clock
相同功能函数,并替换代码中的 clock
调用,有两种实现方案,目前采取方案一
2.1 方案一
使用静态变量特性初始化进程启动时间,每次获取 clock
时用当前时间减启动时间
#include <windows.h>
#include <time.h>
/*
* 获取系统时间
*/
static unsigned __int64 GetCurSystemTime()
{
FILETIME ct;
::GetSystemTimeAsFileTime(&ct);
return (unsigned __int64)ct.dwLowDateTime + (((unsigned __int64)ct.dwHighDateTime) << 32);
}
// 进程启动时间
static unsigned __int64 g_ui64ProcessStartTime = GetCurSystemTime();
/*
* clock函数实现
*/
clock_t GetClock()
{
FILETIME ct;
::GetSystemTimeAsFileTime(&ct);
unsigned __int64 ui64CurTime = (unsigned __int64)ct.dwLowDateTime + (((unsigned __int64)ct.dwHighDateTime) << 32);
ui64CurTime -= g_ui64ProcessStartTime;
return (clock_t)(ui64CurTime / 10000);
}
效率:
VS2017测试,CPU i7-4790:
GetClock
:10w次/1ms- 系统提供的
clock
:10w次/2~3ms
可以看出比系统的 clock
更快(其实也正常,看C++ crt代码就知道哪种更快)
2.2 方案二
用VS2008中的源码的实现方法,把初始化进程启动时间放到 crt
初始化时获取,其它和方案一一样
#include <windows.h>
#include <time.h>
// 自定义一个段
#pragma section(".CRT$XCTMY",long,read)
typedef int(__cdecl *_PIFV)(void);
static unsigned __int64 g_ui64ProcessStartTime; // 进程启动时间
/*
* 初始化进程启动时间
*/
int __cdecl InitProcessStartTime()
{
FILETIME ct;
::GetSystemTimeAsFileTime(&ct);
g_ui64ProcessStartTime = (unsigned __int64)ct.dwLowDateTime + (((unsigned __int64)ct.dwHighDateTime) << 32);
return 0;
}
// 将`__InitProcessStartTime`函数定义在自定义段中
__declspec(allocate(".CRT$XCTMY")) _PIFV __InitProcessStartTime = InitProcessStartTime;
/*
* clock函数实现
*/
clock_t GetClock()
{
FILETIME ct;
::GetSystemTimeAsFileTime(&ct);
unsigned __int64 ui64CurTime = (unsigned __int64)ct.dwLowDateTime + (((unsigned __int64)ct.dwHighDateTime) << 32);
ui64CurTime -= g_ui64ProcessStartTime;
return (clock_t)(ui64CurTime / 10000);
}
3 其它的思考
之前在想能否使用 Windows
的 timeGetTime
去实现
timeGetTime:返回Windows启动到当前的时间(毫秒),每49.71天就会再绕回0
https://docs.microsoft.com/en-us/previous-versions/ms713418(v=vs.85)
但有个问题,Windows开启超过49.71天后会溢出,限制比较大