IBM Z and LinuxONE - Languages - Group home

Beware the trap of time functions

  

When we develop applications in the C programming language, we often use C time library functions, such as time, localtime, ctime, mktime, and asctime, to get or print out time-related information. But you might not notice some interesting phenomenon. Let's first take a look at the following example:

 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 }


Before checking the output of this program, let's consider the following questions:

(1) What are the relationships among the values of the structure variables tm_1, tm_2, and tm_3 on line 22?

(2) On line 23-26, is the time of tm_2 really three seconds later than that of tm_1?

Now, let's take a look at the output of the code above:

-------------------- 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


Is the result the same as what you expected? Actually, tm_1, tm_2 and tm_3 on line 22 point to the same location. In the implementation of the localtime function, a static internal struct tm structure is used to store corresponding time information. Each time the localtime function is invoked, the internal struct tm structure will be modified. That is, this structure stores only the latest invocation result. Therefore, the localtime function returns the same struct tm structure each time. It means that the subsequent invocation of the asctime function on line 23-25 actually pass the same structure (i.e. the pointer that points to the same location). As a result, it is no wonder that they print out the same time.


Let's take a look at another code example:

 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));

 

The code on line 1-28 is the same as the first code example, so let's focus on the rest of this code example. Since the localtime function returns the same internal static structure address, you might want to assign it to a local structure. By doing so, the previous value of the localtime function can be stored, and will not be overwritten by a subsequent invocation, as shown by line 35-36. Then, you might wonder: Will the time of tm_4 and tm_5 printed out by line 41 be the same as the result printed out by line 44-45?


Let's take a look at the output of PARTII:

-------------------- 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

 

Is the output the same as what you expected? To our surprise, the time strings of tm_4 and tm_5 printed by line 41 are the same. However, we know that tm_4 and tm_5 are different time structures, by printing out the address of the tm_4 and tm_5 structures and their corresponding second number(tm.sec). Actually, the problem is caused by the asctime function. By printing out the returned pointer addresses after the asctime function call tm_4 and tm-5, we find that the returned addresses are the same. Judging from the behavior of the asctime function, we can tell that its internal implementation is actually similar to that of the localtime function, which also uses an internal static character array to store converted time strings. Each time the asctime function is invoked, this string will be modified, and this function will return the same address, the address of the internal static character array.


Let's analyze what line 41 does:

  1. Call asctime(&tm_4), update the internal static character array based on tm_4, and return the address the character array;
  2. Call asctime(&tm_5), update the internal static character array based on tm_5, and return the address of the character array. In this step, the new tm_5 information will overwrite the original tm_4 information.
  3. Print out the string that is pointed to by the first argument (i.e. the internal static character array). At this time, this string has been updated with the information of tm_5. Then it prints out the time information of tm_5.
  4. Print out the string that is pointed to by the second argument (i.e. the time of tm_5).  


On line 44, we print out the time information of tm_4 immediately after calling the asctime function. Similarly to what we do with the localtime function, we can use local character array to store the result of calling the asctime function. Then, the result will avoid being written by a subsequent function invocation.


According to the POSIX standard, time functions, such as asctime(), ctime(), gmtime(), and localtime(), return internal static object, either a struct tm structure or character array. Therefore, we should be very cautious when calling these functions. If you do not need to store the invocation result, you can print it out in time; otherwise, you can use a local structure to store it temporarily.


References:

For details about the asctime function, visit the topic at http://linux.die.net/man/3/asctime.

For details about the localtime function, visit the topic at http://linux.die.net/man/3/localtime.