淘先锋技术网

首页 1 2 3 4 5 6 7

如果想知道一段代码运行了多久,要在 log 文件中记录事件发生时的时间戳;再比如说需要一个定时器以便能够定时做某些计算机操作呢?

在计算机世界中,时间在不同场合也往往有不同的含义,让试图思考它的人,感到捉不透它。但值得庆幸的是,Linux 中的时间终究是可以理解的。下面尝试着深入理解 Linux 系统中 C 语言编程中的时间问题。

获取当前时间

在程序当中, 我们经常要输出系统当前的时间,比如日志文件中的每一个事件都要记录其产生时间。在 C 语言中获取当前时间的方法有以下几种,它们所获得的时间精度从秒级到纳秒,各有所不同。

time() 函数获取当前时间

time函数会返回从公元1970年1月1日的UTC时间从0时0分0秒算起到现在所经过的秒数。如果t 并非空指针的话,此函数也会将返回值存到t指针所指的内存。

SYNOPSIS
       #include <time.h>

       time_t time(time_t *t);

DESCRIPTION
       time() returns the time as the number of seconds since the Epoch, 1970-01-01 00:00:00 +0000 (UTC).

RETURN VALUE
       On  success,  the value of time in seconds since the Epoch is returned.  On error, ((time_t) -1) is returned, and errno is
       set appropriately.
ERRORS
       EFAULT t points outside your accessible address space.
    //成功返回秒数,错误则返回(time_t) -1),错误原因存于errno中

来看看一个获取当前时间小例子:

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

int main(int argc , char *argv[])
{
    time_t seconds;

    seconds = time((time_t *) NULL);
    printf("time = %d\n",seconds);


    return 0;
}

编译输出:
在这里插入图片描述

gettimeofday() 获取当前时间

gettimeofday函数获取当前时间存于tv结构体中,相应的时区信息则存于tz结构体中。这里需要注意的是tz是依赖于系统,不同的系统可能存在获取不到的可能,因此通常设置为NULL

函数原型

#include <sys/time.h>

    int gettimeofday(struct timeval *tv, struct timezone *tz);

struct timeval {
               time_t      tv_sec;     /* seconds (秒)*/
               suseconds_t tv_usec;    /* microseconds(微秒) */
           };
struct timezone {
               int tz_minuteswest;     /* minutes west of Greenwich */
               int tz_dsttime;         /* type of DST correction */
           };

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

int main(int argc , char *argv[])
{

    struct timeval tv;

    gettimeofday(&tv,NULL);

    printf("tv_sec: %d\n",tv.tv_sec);
    printf("tv_usec: %d\n",tv.tv_usec);

    return 0;
}

编译输出:
在这里插入图片描述

clock_gettime() 获取精确时间函数

clock_gettime() 函数是基于linux 操作系统的。可以根据需要,获取不同要求的精确时

函数原型:


#include <time.h>

int clock_gettime(clockid_t clk_id, struct timespec* tp);

clk_id : 检索和设置的clk_id指定的时钟时间。
CLOCK_REALTIME:系统实时时间,随系统实时时间改变而改变,即从UTC1970-1-1 0:0:0开始计时,
中间时刻如果系统时间被用户改成其他,则对应的时间相应改变
  CLOCK_MONOTONIC:从系统启动这一刻起开始计时,不受系统时间被用户改变的影响
  CLOCK_PROCESS_CPUTIME_ID:本进程到当前代码系统CPU花费的时间
  CLOCK_THREAD_CPUTIME_ID:本线程到当前代码系统CPU花费的时间
struct timespec
{
        time_t tv_sec; /* 秒*/
        long tv_nsec; /* 纳秒*/
};

举个例子:

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

int main(int argc, char *argv[])
{
	time_t dwCurTime1;
	dwCurTime1 = time(NULL);

    struct timeval stCurTime2;
    gettimeofday(&stCurTime2, NULL);

    struct timespec stCurTime3;
    clock_gettime(CLOCK_REALTIME, &stCurTime3);

    printf("Time1 = %d s\n",dwCurTime1);
    printf("Time2 = %d s\n",stCurTime2.tv_sec);
    printf("Time3 = %d s\n",stCurTime3.tv_sec);

    printf("Time2 = %d us\n",stCurTime2.tv_usec);
    printf("Time3 = %d ns\n",stCurTime3.tv_nsec); 

	return 0;
}


编译输出:

在这里插入图片描述

上面介绍的三个API是GUN/Linux 提供了三个标准的 API 用来获取当前时间,time()/gettimeofday()/clock_gettime(),它们的区别仅在于获取的时间精度不同,可以根据需要选取合适的调用。

时间显示和转换

目前我们得到的时间是一个数字,无论精度如何,它代表的仅是一个差值。比如精度为秒的 time() 函数,返回一个 time_t 类型的整数。

在这里插入图片描述

假设当前时间为 2020 年 6 月 21 日下午 12 点 16 分 51 秒,那么 time_t 的值为:1592713009
。即距离 1970 年 1 月 1 日零点,我们已经过去了 1592713009秒。这里的 1970 年 1 月 1 日零点是格林威治时间,而不是北京时间。下面讨论的时间如果不特别说明都是格林威治时间,也叫 GMT 时间,或者 UTC 时间。

字符串“1592713009秒”对于多数人都没有太大的意义,我们更愿意看到“2020 年 6 月 21 日”这样的显示。因此当我们得到秒,毫秒,甚至纳秒表示的当前时间之后,往往需要将这些数字转换为人们所熟悉的时间表示方法。

各种时间显示格式转换函数关系图

由于国家,习惯和时区的不同,时间的表示方法并没有一个统一的格式。为了满足各种时间显示的需求,标准 C 库提供了许多时间格式转换的函数。这些函数的数量众多,容易让人迷惑,记住它们的用法十分不易。来看看一张图:

在这里插入图片描述
图片来源:《Linux Programming Interface》

从上图可以看到,time()/gettimeofday() 从内核得到当前时间之后,该当前时间值可以被两大类函数转换为更加容易阅读的显示格式,分别为固定格式转换和用户指定格式转换函数。

固定格式转换

用 ctime() 函数转换出来的时间格式是系统固定的,调用者无法改动,因此被称为固定格式转换。如果您对日期格式没有特殊的要求,那么用它基本上就可以了

举个例子,固定格式打印时间:

#include <stdio.h>
#include <time.h>
 
int main (int argc ,char *argv[])
{
   time_t curtime;
 
   time(&curtime); //获取当前时间
 
   printf("curtime = %s", ctime(&curtime));
 
   return 0;
}

编译输出:

在这里插入图片描述

自定义格式转换

为了更灵活的显示,需要把类型 time_t 转换为 tm 数据结构。tm 数据结构将时间分别保存到代表年,月,日,时,分,秒等不同的变量中。不再是一个令人费解的 64 位整数了。这种数据结构是各种自定义格式转换函数所需要的输入形式。

struct tm结构:

struct tm {
               int tm_sec;         /* seconds */
               int tm_min;         /* minutes */
               int tm_hour;        /* hours */
               int tm_mday;        /* day of the month */
               int tm_mon;         /* month */
               int tm_year;        /* year */
               int tm_wday;        /* day of the week */
               int tm_yday;        /* day in the year */
               int tm_isdst;       /* daylight saving time */
           };

//int tm_sec 代表目前秒数,正常范围为0-59,但允许至61秒
//int tm_min 代表目前分数,范围0-59
//int tm_hour 从午夜算起的时数,范围为0-23
//int tm_mday 目前月份的日数,范围01-31
//int tm_mon 代表目前月份,从一月算起,范围从0-11
//int tm_year 从1900 年算起至今的年数
//int tm_wday 一星期的日数,从星期一算起,范围为0-6
//int tm_yday 从今年1月1日算起至今的天数,范围为0-365
//int tm_isdst 日光节约时间的旗标

可以使用 gmtime() 和 localtime() 把 time_t 转换为 tm 数据格式,其中 gmtime() 把时间转换为格林威治时间;localtime 则转换为当地时间。

下面的实例演示了 gmtime() 函数的用法。

#include <stdio.h>
#include <time.h>
 
#define BST (+1)
#define CCT (+8)
 
int main ()
{
 
   time_t rawtime;
   struct tm *info;
 
   time(&rawtime); //
   /* 获取 GMT 时间 */
   info = gmtime(&rawtime );
   
   printf("当前的世界时钟:\n");
   printf("London: %2d:%02d\n", (info->tm_hour+BST)%24, info->tm_min);
   printf("China: %2d:%02d\n", (info->tm_hour+CCT)%24, info->tm_min);
 
   return(0);
}

编译输出:
在这里插入图片描述

从以上的例子可以看到,利用从 time() 得到的时间值,可以调用各种转换函数将其转换成更方便我们阅读的形式。

从前面的总结中我们也了解到,还有两个 C 函数可以获得当前时间,gettimeofday() 以及 clock_gettime(),它们分别返回 struct timeval 或者 timespec 代表的高精度的时间值。

在目前的 GLibC 中,还没有直接把 struct timeval/timespec 转换为 struct tm 的函数。一般的做法是将 timeval 中的 tv_sec 转换为 tm,使用上面所述的方法转换为字符串,最后在显示的时候追加上 tv_usec,比如下面的例子代码:

#include <stdio.h>
#include <time.h>
#include <string.h>
#include <sys/time.h>
#include <stdio.h>
 
int main (int argc ,char *argv[])
{

    struct timeval tv;
    time_t nowtime; 
    struct tm *nowtm; 
    char tmbuf[64], buf[64]; 

    gettimeofday(&tv, NULL); //获取当前时间到 tv
    nowtime = tv.tv_sec; //nowtime 存储了秒级的时间值
    nowtm = localtime(&nowtime); //转换为 tm 数据结构
    //用 strftime 函数将 tv 转换为字符串,但 strftime 函数只能达到秒级精度
    strftime(tmbuf, sizeof tmbuf, "%Y-%m-%d %H:%M:%S", nowtm);
    printf("tmbuf = %s\n",tmbuf);
    //将毫秒值追加到 strftime 转换的字符串末尾 
    snprintf(buf, sizeof buf, "%s.%06d", tmbuf, tv.tv_usec);
    printf("buf = %s\n",buf);
 
   return(0);
}

编译输出:

在这里插入图片描述

高精度的时间获取方式

有时候我们要计算某段程序执行的时间,比如需要对算法进行时间分析。基本的实现思路为在被测试代码的开始和结束的地方获取当时时间,相减后得到相对值,即所需要的统计时间。为了实现高精度的时间测量,必须使用高精度的时间获取方式。例如使用gettimeofday。

gettimeofday() 获取当前时间

gettimeofday函数获取当前时间存于tv结构体中,相应的时区信息则存于tz结构体中,需要注意的是tz是依赖于系统,不同的系统可能存在获取不到的可能,因此通常设置为NULL 。

函数原型:

#include <sys/time.h>

    int gettimeofday(struct timeval *tv, struct timezone *tz);

struct timeval {
               time_t      tv_sec;     /* seconds (秒)*/
               suseconds_t tv_usec;    /* microseconds(微秒) */
           };
struct timezone {
               int tz_minuteswest;     /* minutes west of Greenwich */
               int tz_dsttime;         /* type of DST correction */
           };

这个函数获取从1970年1月1日到现在经过的时间和时区(UTC时间),(按照linux的官方文档,时区已经不再使用,正常应该传NULL)。

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

void function()
{
    unsigned int i,j; 
    double y; 
    for(i=0;i<1000;i++) 
    for(j=0;j<1000;j++) 
    y=sin((double)i); //耗时操作
}


int main(int argc, char *argv[])
{
    
    struct timeval tpstart,tpend; 
    float timeuse; 

    gettimeofday(&tpstart,NULL); //记录开始时间戳
    function(); 
    //2次调用 gettimeofday,然后计算的差值就是时间间隔 
    gettimeofday(&tpend,NULL); //记录结束时间戳

    timeuse = 1000000*(tpend.tv_sec-tpstart.tv_sec)+ 
    tpend.tv_usec-tpstart.tv_usec; //计算差值
    timeuse /= 1000000; 
    printf("Used Time:%f\n",timeuse); 

  
    return 0;
}

编译输出:
在这里插入图片描述

这个程序输出函数的执行时间,我们可以使用这个来进行系统性能的测试,或者是函数算法的效率分析。

也可以使用time()计算精度,但是精度很低(秒级),不建议使用,这里就稍微带下用法。

    time_t start,stop;
    start = time(NULL);
    function();
    stop = time(NULL);

总结

本篇讲解Linux 系统中 C 语言编程中的时间问题,主要讲解获取当前时间、时间显示和转换、 高精度的时间获取方式,希望本篇对你有帮助。

在这里插入图片描述
欢迎关注微信公众号【程序猿编码】,添加本人微信号(17865354792),回复:领取学习资料。或者回复:进入技术交流群。网盘资料有如下:

在这里插入图片描述