聊一聊Linux C中時間編程

如果想知道一段代碼運行了多久,要在 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),回覆:領取學習資料。或者回復:進入技術交流羣。網盤資料有如下:

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章