C语言localtime和asctime时间函数中的陷阱

晚上调试程序,碰到一个奇怪的问题:

在程序的多个地方调用了time和localtime函数获得当前系统时间,然后在程序运行结束后一次性打印程序前面几次获得的系统时间,但是打印出来的几处时间居然都是最后一个时刻的时间。后来尝试把每次获得的时间通过本地变量保存一份,统一打印的时候打印本地保存的变量值,才能输出期望的结果。

关于这个问题在网上找到了一篇解释:

在编写C语言的应用程序时,为了获取或者打印一些跟时间有关的信息,我们经常会使用到C语言自带的一些时间函数,诸如:time、localtime、ctime、mktime和asctime等。但你可能没有注意到这里面含有一些有趣的现象,先来看一个例子:

  #include <stdio.h> 
  #include <time.h> 
  
  int main () 
  { 
  
     time_t time_1, time_2; 
     struct tm *tm_1, *tm_2, *tm_3; 
     struct tm tm_4, tm_5; 
  
     printf("-------------------- PART I -------------------\n"); 
  
     time_1 = time(NULL); 
     sleep(3); 
     time_2 = time(NULL); 
     printf("time1:%d time2:%d\n",time_1,time_2); 
  
     tm_1 = (struct tm*)localtime(&time_1); 
     tm_2 = (struct tm*)localtime(&time_2); 
     tm_3 = (struct tm*)localtime(&time_1); 
  
     printf("tm_1 ptr:%p tm_2 ptr:%p tm_3 ptr:%p\n",tm_1,tm_2,tm_3); 
     printf("asctime(tm_1):%s",asctime(tm_1)); 
     printf("asctime(tm_2):%s",asctime(tm_2)); 
     printf("asctime(tm_3):%s",asctime(tm_3)); 
  } 
 


       在看这段代码的输出结果之前,先问大家两个问题: 
        (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. asctime函数说明 http://linux.die.net/man/3/asctime 
2. localtime函数说明 http://linux.die.net/man/3/localtime 

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值