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/Unix:
clock()返回的是进程消耗的CPU时间,不包括睡眠时间。如果你调了sleep(1),clock()几乎不会增加。 - Windows:
clock()返回的是墙上时间(挂钟时间),包括睡眠时间。行为完全不同。 - 某些嵌入式系统:
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() 来替代。
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 知识体系图
下面这张图帮你理清这三个函数的关系和适用场景。
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位时间类型。
时间函数看似简单,但跨平台时细节很多。记住一句话:用对函数,比用高精度函数更重要。精度再高,行为不一致也是白搭。