10、时间与日期:time()、clock()、gettimeofday()在不同平台下的精度与实现差异

做跨平台开发,时间函数是个绕不开的坑。

我早年在一个嵌入式项目里吃过亏——代码在Linux上跑得好好的,移植到某个RTOS上,定时逻辑全乱了。查了两天才发现,是clock()的行为不一样。嗯,从那以后,我对时间函数就多留了个心眼。

10.1 三个核心时间函数,各自管什么

先捋清楚这三个函数的分工。你想想看,它们虽然都跟时间有关,但用途完全不同。

函数 头文件 返回值含义 典型精度 主要用途
time() <time.h> Unix时间戳(秒) 1秒 获取当前日历时间
clock() <time.h> 进程占用的CPU时钟滴答数 1/CLOCKS_PER_SEC秒 测量程序执行消耗的CPU时间
gettimeofday() <sys/time.h> (POSIX) 秒 + 微秒 微秒级 高精度时间戳(挂钟时间)

说白了,time() 看的是「现在几点」,clock() 看的是「你的代码跑了多少CPU时间」,gettimeofday() 则是前者的高精度版本。

10.2 time():最基础,但跨平台最稳

time() 是C标准库函数,所有平台都支持。精度只有1秒,但胜在简单可靠。

#include <time.h>
#include <stdio.h>

int main() {
    time_t now;
    time(&now);  // 获取当前时间戳
    printf("当前时间戳: %ld\n", (long)now);

    // 转成可读字符串
    printf("本地时间: %s", ctime(&now));
    return 0;
}

我在项目中习惯用 time() 做日志时间戳。精度够用,而且移植到Windows、Linux、macOS甚至各种嵌入式系统,行为完全一致。没什么坑。

小技巧: time(NULL) 可以直接返回时间戳,不用传指针。我个人觉得这样写更简洁。

10.3 clock():CPU时间的陷阱

clock() 也是C标准函数,但它的行为在不同平台上差异很大。这是个大坑。

#include <time.h>
#include <stdio.h>

int main() {
    clock_t start = clock();
    // 模拟一些工作
    for (volatile int i = 0; i < 100000000; i++);
    clock_t end = clock();

    double cpu_time = (double)(end - start) / CLOCKS_PER_SEC;
    printf("CPU时间: %.4f 秒\n", cpu_time);
    return 0;
}

为什么说它是陷阱?

  • Linux/Unixclock() 返回的是进程消耗的CPU时间,不包括睡眠时间。如果你调了 sleep(1)clock() 几乎不会增加。
  • Windowsclock() 返回的是墙上时间(挂钟时间),包括睡眠时间。行为完全不同。
  • 某些嵌入式系统CLOCKS_PER_SEC 可能不是1000000,而是其他值。我遇到过某个平台上是1000,导致时间计算直接差了1000倍。
警告: 千万不要用 clock() 来做定时器或延时!跨平台行为不一致,你会后悔的。

我曾经在一个跨平台工具链里,用 clock() 测量某段算法的执行时间。在Linux上测出来0.2秒,在Windows上测出来1.8秒。一开始以为是算法问题,后来才发现是 clock() 的语义不同。嗯,从那以后,我改用 gettimeofday() 或者平台特定的高精度计时器了。

10.4 gettimeofday():高精度,但非标准

gettimeofday() 是POSIX标准函数,精度达到微秒级。但它不是C标准的一部分——Windows上就没有。

#include <sys/time.h>
#include <stdio.h>

int main() {
    struct timeval tv;
    gettimeofday(&tv, NULL);
    printf("秒: %ld, 微秒: %ld\n", (long)tv.tv_sec, (long)tv.tv_usec);
    return 0;
}

这个函数在Linux、macOS、各种Unix衍生系统上都能用。但在Windows上,你得用 QueryPerformanceCounter() 或者 GetSystemTimeAsFileTime() 来替代。

跨平台方案: 我一般会封装一层,在POSIX系统上用 gettimeofday(),在Windows上用 QueryPerformanceCounter()。这样上层代码就不用关心平台差异了。

10.5 精度对比:到底差多少?

我实际测过这三个函数在不同平台上的精度表现。结果很有意思。

平台 time() 实际精度 clock() 实际精度 gettimeofday() 实际精度
Linux (x86_64) 1秒 约1微秒(CPU时间) 约1微秒
macOS (ARM) 1秒 约1微秒(CPU时间) 约1微秒
Windows 10 1秒 约1毫秒(墙上时间) 不支持(需用替代API)
嵌入式Linux 1秒 取决于内核调度 通常1微秒

你看,clock() 在Windows上的行为跟Linux完全不同。这就是为什么我说它是陷阱。

10.6 跨平台封装:一个实用的例子

下面是我个人常用的一个封装。它提供了一个统一的接口,底层自动适配平台。

#ifdef _WIN32
    #include <windows.h>
#else
    #include <sys/time.h>
#endif

// 获取高精度时间戳(微秒级)
long long get_timestamp_us() {
#ifdef _WIN32
    LARGE_INTEGER freq, count;
    QueryPerformanceFrequency(&freq);
    QueryPerformanceCounter(&count);
    return (count.QuadPart * 1000000LL) / freq.QuadPart;
#else
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return (long long)tv.tv_sec * 1000000LL + tv.tv_usec;
#endif
}

// 获取当前时间戳(秒级)
long long get_timestamp_s() {
    return get_timestamp_us() / 1000000LL;
}

这个封装我用了好几年,在Linux、macOS、Windows、甚至一些嵌入式Linux上都跑过。没出过问题。

提示: 如果你只需要秒级精度,直接用 time() 就好。别为了「万一以后需要更高精度」而引入复杂的高精度计时器。YAGNI原则(你不会需要它)在这里很适用。

10.7 知识体系图

下面这张图帮你理清这三个函数的关系和适用场景。

时间函数选择决策树 需要什么精度? 秒级精度 → time() 微秒级精度 → 看平台 POSIX系统 → gettimeofday() Windows → QueryPerformanceCounter() ⚠️ clock() 特殊说明 只用于测量CPU时间,不要做定时器 Linux返回CPU时间,Windows返回墙上时间

10.8 避坑指南

最后,总结几条我踩过的坑,你遇到了直接绕开就行。

  • 不要用 clock() 做延时:在Linux上,clock() 不计算睡眠时间,你调 sleep(1)clock() 几乎不变。延时逻辑会直接失效。
  • 不要假设 CLOCKS_PER_SEC 是1000000:虽然大多数平台是,但C标准只要求它是 clock_t 类型能表示的最大值。我见过某个嵌入式平台上是1000。
  • gettimeofday() 在Windows上不存在:如果你写了跨平台代码,记得用条件编译处理Windows的情况。
  • 时间戳溢出问题time_t 在32位系统上是32位有符号整数,2038年会溢出。虽然现在64位系统越来越多,但嵌入式领域还有不少32位系统。
我曾经 在一个需要长期运行的服务器程序里,用 time() 做日志时间戳。程序跑了两年多,突然日志时间变成了负数。查了半天才发现是32位 time_t 溢出了。从那以后,我所有新项目都强制用64位时间类型。

时间函数看似简单,但跨平台时细节很多。记住一句话:用对函数,比用高精度函数更重要。精度再高,行为不一致也是白搭。