程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 小心C語言時間函數陷阱

小心C語言時間函數陷阱

編輯:關於C語言

在編寫C語言的應用程序時,為了獲取或者打印一些跟時間有關的信息,我們經常會使用到C語言自帶的一 些時間函數,諸如:time、localtime、ctime、mktime和asctime等。但你可能沒有注意到這裡面含有一些有 趣的現象,先來看一個例子:

1 #include <stdio.h>
2 #include <time.h>
3
4 int main ()
5 {
6
7 time_t time_1, time_2;
8 struct tm *tm_1, *tm_2, *tm_3;
9 struct tm tm_4, tm_5;
10
11 printf("-------------------- PART I -------------------\n");
12
13 time_1 = time(NULL);
14 sleep(3);
15 time_2 = time(NULL);
16 printf("time1:%d time2:%d\n",time_1,time_2);
17
18 tm_1 = (struct tm*)localtime(&time_1);
19 tm_2 = (struct tm*)localtime(&time_2);
20 tm_3 = (struct tm*)localtime(&time_1);
21
22 printf("tm_1 ptr:%p tm_2 ptr:%p tm_3 ptr:%p\n",tm_1,tm_2,tm_3);
23 printf("asctime(tm_1):%s",asctime(tm_1));
24 printf("asctime(tm_2):%s",asctime(tm_2));
25 printf("asctime(tm_3):%s",asctime(tm_3));
26 }

在看這段代碼的輸出結果之前,先問大家兩個問題:

 (1) 第22行,struct tm結構體 tm_1、tm_2 和tm_3的值有什麼關系?

 (2) 第23-26行的輸出結果中,tm_2的時間真的比tm_1晚3秒嗎?

接下來 ,我們來看一下這段代碼的輸出結果:

-------------------- PART I -------------------

time1:1340256774 time2:1340256777

tm_1 ptr:0xfec6f48 tm_2 ptr:0xfec6f48 tm_3 ptr:0xfec6f48

asctime(tm_1):Thu Jun 21 01:32:54 2012

asctime(tm_2):Thu Jun 21 01:32:54 2012

asctime(tm_3):Thu Jun 21 01:32:54 2012

這裡的打印結果是否跟你前面預想的一樣呢?沒錯 ,第22行中的tm_1、tm_2和tm_3其實指向的是同一個地址。在localtime函數的實現中,采用了一個靜態內部 struct tm結構體來存儲對應的時間信息。每次對localtime函數的調用,都將會修改內部這個struct tm結構 體,也就是說,這個結構體將只會保存最新的調用結果。因此,localtime每次返回的struct tm結構體也將是 同一個,即指向的地址是同一個。這也就意味著,後續第23行到第25行對asctime的調用中,實際上傳入的都 是同一個結構體(指向同一個地址的指針),結果它們打出來的時間一樣也就不足為奇了。

我們再來 看以下這段代碼:

1 #include <stdio.h>
2 #include <time.h>
3
4 int main ()
5 {
6
7 time_t time_1, time_2;
8 struct tm *tm_1, *tm_2, *tm_3;
9 struct tm tm_4, tm_5;
10
11 printf("-------------------- PART I -------------------\n");
12
13 time_1 = time(NULL);
14 sleep(3);
15 time_2 = time(NULL);
16 printf("time1:%d time2:%d\n",time_1,time_2);
17
18 tm_1 = (struct tm*)localtime(&time_1);
19 tm_2 = (struct tm*)localtime(&time_2);
20 tm_3 = (struct tm*)localtime(&time_1);
21
22 printf("tm_1 ptr:%p tm_2 ptr:%p tm_3 ptr:%p\n",tm_1,tm_2,tm_3);
23 printf("asctime(tm_1):%s",asctime(tm_1));
24 printf("asctime(tm_2):%s",asctime(tm_2));
25 printf("asctime(tm_3):%s",asctime(tm_3));
26
27
28 printf("-------------------- PART II -------------------\n");
29
30 time_1 = time(NULL);
31 sleep(3);
32 time_2 = time(NULL);
33 printf("time1:%d time2:%d\n",time_1,time_2);
34
35 tm_4 = *((struct tm*)localtime(&time_1));
36 tm_5 = *((struct tm*)localtime(&time_2));
37
38 printf("tm_4 ptr:%p tm_5 ptr:%p\n",&tm_4,&tm_5);
39 printf("tm_4 sec:%d tm_5 sec:%d\n",tm_4.tm_sec,tm_5.tm_sec);
40
41 printf("asctime(&tm_4):%sasctime(&tm_5):%s",asctime(&tm_4),asctime (&tm_5));
42 printf("asctime(&tm_4) ptr:%p asctime(&tm_5) ptr:%p\n",asctime (&tm_4),asctime(&tm_5));
43
44 printf("asctime(&tm_4):%s",asctime(&tm_4));
45 printf("asctime(&tm_5):%s",asctime(&tm_5));

在第28行之前跟上面的示例代碼是一樣,這裡就不再冗述,我們主要來看剩余部分。既然在前面我們已經 知道了localtime返回的是同一個內部靜態結構的地址,那麼我們不禁想到可以將它賦值給本地結構體,這樣 前面對localtime函數的返回值可以得到保存,不會被後面的調用所覆蓋,如第35和36行所示。那麼,我們不 禁想問:第41行打印出來的結構體tm_4和tm_5對應的時間結果跟第44-45行打印出來的一樣麼?

我們來 看一下PART II的輸出結果:

-------------------- PART II -------------------

time1:1340256777 time2:1340256780

tm_4 ptr:0xffe82dd8 tm_5 ptr:0xffe82e08

tm_4 sec:57 tm_5 sec:0

asctime(&tm_4):Thu Jun 21 01:33:00 2012

asctime(&tm_5):Thu Jun 21 01:33:00 2012

asctime(&tm_4) ptr:0xfec59dc asctime(&tm_5) ptr:0xfec59dc

asctime (&tm_4):Thu Jun 21 01:32:57 2012

asctime(&tm_5):Thu Jun 21 01:33:00 2012

輸出結 果跟你的預期一樣麼?我們驚奇地發現,第41行打印的結果中,tm_4和tm_5的時間字符串是一樣的。但我們通 過打印出tm_4和tm_5結構體的地址,以及對應的秒數(tm.sec),都可以讓我們確信這次tm_4和tm_5是兩個不 同的時間結構體。問題其實出在asctime函數中,通過打印asctime調用tm_4和tm_5結構體後返回的指針地址, 我們發現這兩個地址是一樣的。從asctime函數的行為,我們不難聯想到,其實它的內部實現跟localtime類似 ,它也使用了一個內部靜態字符數組來保存轉換好的時間字符串。每次對asctime的調用,都將修改這個字符 串,而函數每次都將返回同一個地址,即內部靜態字符數組的地址。

下面我們來分析一下第41行的行 為:

1) 調用asctime(&tm_4),按照tm_4的信息更新內部靜態字符數組,返回字符數組的地址;

2) 調用asctime(&tm_5),按照tm_5的信息更新內部靜態字符數組,返回字符數組的地址。在這一步中 ,新的tm_5的信息將會覆蓋掉原來tm_4的那些信息。

3) 打印第一個參數指向地址的字符串,即該內部靜態 字符數組。此時這個字符串已經被更新為tm_5的信息了,因此,將打印tm_5對應的時間信息。

4) 打印第二 個參數指向地址的字符串,即tm_5對應的時間信息。

而在第44行中,我們在調用asctime函數後,隨即 將信息打印出來,此時則為tm_4的時間信息。同理,跟我們對localtime函數的操作類似,我們也可以通過本 地字符數組來保存asctime函數的調用結果,從而避免結果被後續的調用所覆蓋。

根據POSIX標准,時 間函數asctime()、ctime()、gmtime()和localtime()函數都將返回內部靜態對象,要麼是struct tm結構體, 要麼是字符數組。因此,在對這些函數的調用時,我們需要額外的小心,如果不需要保存其調用結果,我們可 以及時地打印,如果需要保存其調用結果,可以采用本地結構來臨時存放。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved