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