Exploring C++11, part 2 (localtime and time again)

C++11 time, part II. Part I you can read here:
Several of the time functions in C++ are not thread-safe and using for example std::localtime or std::asctime can have unwanted side-effects. Other thread unsafe c-library functions that come to mind are std::ctime and std::gmtime. The compiler will likely point out any use of these unsafe functions by generating warnings.

It is nice to know that there is an easy way to make your time code thread-safe and as a bonus getting rid of the compiler warnings.

Depending on your platform the std::localtime can be replaced for platform dependent localtime_r or localtime_s.

std::asctime and std::ctime have _r and _s equivalents but a much better option is to use the std library functions std::strftime and std::put_time.

That is really all you need to avoid problems. Do you want to know details? If so, read on …


First, let us see how localtime and asctime are commonly used, and why they are not safe.

localtime

std::tm* localtime( const std::time_t *time )
localtime takes the std::time_t* parameter and fills an internal struct std::tm with date and time components. The return std::tm* points to the internal, static std::tm struct adjusted with timezone and daylight saving for the local representation of the given time.

Warning: localtime returns a pointer to a static buffer (std::tm*). Another thread can call the function and the static buffer could be overwritten before the first thread has finished reading the content of the struct std::tm*.

asctime

char* asctime( const std::tm* time_ptr )
asctime takes the std::tm* parameter and converts it to a const char* string for easy readability. The returned string has the following format: Www Mmm dd hh:mm:ss yyyy

Warning: asctime returns a pointer to a static string. Another thread can call asctime or ctime and the static string could be overwritten before the first thread has finished reading the content of the string.

Example of std::localtime and std::asctime
All code in the example here and below are written explicitly. I.e. auto is avoided.

#include <ctime>
#include <iostream>

int main()
{
  std::time_t t = std::time(nullptr);
  std::cout << std::asctime(localtime(&t)) << '\n';
  return 0;
}

Output on my computer

Sun Jan 20 21:59:04 2013

Online executable of “Example of std::localtime and std::asctime”: http://liveworkspace.org/code/2Bu8AV$0

Simulating multiple thread access to localtime

Showing that std::localtime and std::asctime are thread unsafe is easy. In the example below this is done in one thread to simplify things. Instead of trying to get multiple threads to have race conditions with undefined behaviour we can split up the use of std::localtime in a serial way and it should be very obvious how a nice time bug could occur.

thread unsafe localtime

/**
* Get  two std::time_t, 5 seconds apart, and show how they
* when used through localtime seemingly get messed up */
#include <ctime>
#include <iostream>

int main()
{
   std::time_t t1 = std::time(nullptr);
   std::this_thread::sleep_for(std::chrono::seconds(5));
   std::time_t t2 = std::time(nullptr);

   // std::tm* points to static data that will be overwritten
   // for any new localtime call 
   // 1) Print the original value before it is overwritten   
   std::tm*  t1_tm = localtime(&t1);
   std::cout << "t1 (original)   :"<< std::asctime(t1_tm) << '\n';

   // 2) Print t2 and t1 after t1 is overwritten by t2
   std::tm*  t2_tm = localtime(&t2);
   std::cout << "t1 (overwritten):"<< std::asctime(t1_tm);
   std::cout << "t2              :"<< std::asctime(t2_tm);
   
   return 0;
}

Output on my computer. The same value for t1 was not printed twice. Instead the t1_tm was overwritten by the time for t2 as t2_tm was created.

t1 (original)   :Mon Jan 21 13:56:25 2013

t1 (overwritten):Mon Jan 21 13:56:30 2013
t2              :Mon Jan 21 13:56:30 2013

Online executable of “thread unsafe localtime”: http://liveworkspace.org/code/3USwFm$0

thread unsafe asctime

/** asctime returns a static allocated string shared with other calls
* to asctime or ctime. Every time these functions are called the content
* of the static string is overwritten*/
int main()
{
  std::time_t t1 = std::time(nullptr);
  std::tm* t1_tm = std::localtime(&t1);
  const char* t1_as_text = std::asctime(t1_tm); // save as dangerous string
  std::cout << "t1 (original)   : " << t1_as_text << "\n";
 
  std::this_thread::sleep_for(std::chrono::seconds(5));

  std::time_t t2 = std::time(nullptr);
  std::tm* t2_tm = std::localtime(&t2);
  const char* t2_as_text = std::asctime(t2_tm);  // overwrite t1_as_text
 
  std::cout << "t1 (overwritten): " << t1_as_text;
  std::cout << "t2              : " << t2_as_text << std::endl;
  return 0;
}

t1 could be expected to be printed twice, but the second time its string representation is overwritten by t2, which was created 5 seconds later


t1 (original) : Mon Jan 21 13:56:30 2013

t1 (overwrite): Mon Jan 21 13:56:35 2013
t2            : Mon Jan 21 13:56:35 2013

Online executable of “thread unsafe asctime”: http://liveworkspace.org/code/4rbqaK$0

Unsafe to safe

For localtime there is no std alternative but for std::asctime you can and should use either the std::strftime or std::put_time.

std::size_t
strftime ( char* str, std::size_t count, const char* format, std::tm* time )

std::strftime is more c-ish as you have to pre-allocate a character array (char* str) to write into.

template /*unspecified*/
put_time ( const std::tm* time, const charT* format);

std::put_time is cleaner as it only needs the pointer to the std::tm struct and formatting instructions (which std::strftime also needs). There is no need to pre-allocate a character array.

At the time of writing there is only so-so support for std::put_time. At least my current version of gcc 4.7.2 running on Ubuntu 12.10 has no support for std::put_time . On VS2012 there is limited support for std::put_time but only a few of the formatting options work. Using an option that is not yet implemented results in fatal crash of your application.

The safe thing is to use std::strftime for now, both for gcc and VS2012 (and clang?). Or if you can use std::put_time, only use the limited formatting options that you have tested to be available.

Cross-Platform coding made easy

Just wrap std::localtime in your own namespace separated localtime and substitute std::localtime for localtime_s or localtime_r. Do likewise for std::put_time and use std:strftime internally if std::put_time is not available. When std::put_time is up to speed on your platform you can just replace your namespace::put_time for std::put_time.

Here is my take used in the code for the asynchronous “crash safe” logger g2log.

namespace g2{
typedef std::chrono::time_point<std::chrono::system_clock>  system_time_point;

tm localtime(const std::time_t& time)
{
  std::tm tm_snapshot;
#if (defined(WIN32) || defined(_WIN32) || defined(__WIN32__))
  localtime_s(&tm_snapshot, &time); 
#else
  localtime_r(&time, &tm_snapshot); // POSIX  
#endif
  return tm_snapshot;
}


// To simplify things the return value is just a string. I.e. by design!  
std::string put_time(const std::tm* date_time, const char* c_time_format)
{
#if (defined(WIN32) || defined(_WIN32) || defined(__WIN32__))
  std::ostringstream oss;
    
  // BOGUS hack done for VS2012: C++11 non-conformant since it SHOULD take a "const struct tm*  "
  // ref. C++11 standard: ISO/IEC 14882:2011, ยง 27.7.1, 
  oss << std::put_time(const_cast<std::tm*>(date_time), c_time_format); 
  return oss.str();

#else    // LINUX
  const size_t size = 1024;
  char buffer[size]; 
  auto success = std::strftime(buffer, size, c_time_format, date_time); 

  if (0 == success)
    return c_time_format; 
  
  return buffer; 
#endif
}


// extracting std::time_t from std:chrono for "now"
std::time_t systemtime_now()
{
  system_time_point system_now = std::chrono::system_clock::now();
  return std::chrono::system_clock::to_time_t(system_now);
}

} // g2-namespace

Example of thread-safe use of localtime

int main()
{
   using namespace g2; // 'localtime' and 'put_time' are within the g2 namespace
   const char* format = "%c"; 

   std::tm t1 = localtime(systemtime_now());
   std::cout << "t1 (original)        : " << put_time(&t1,  format) << '\n';

   std::this_thread::sleep_for(std::chrono::seconds(5));

   std::tm t2 = localtime(systemtime_now());
   std::cout << "\nt1 (still original): " << put_time(&t1,  format) << '\n';
   std::cout << "t2                   : " << put_time(&t2,  format) << '\n';

   return 0;
}
t1 (original)      : 01/21/13 22:41:32

t1 (still original): 01/21/13 22:41:32
t2                 : 01/21/13 22:41:37

Online executable of “thread-safe use of localtime”: http://liveworkspace.org/code/4xLfSS$0

There you have it. It was a fairly long blog to get to what I stated in the beginning. Stop using std::localtime, std::asctime and other thread unsafe c-functions.

Let us hope that they will be deprecated or replaced (not likely) by the C++ Standards Committee.

Remember to listen to what your compiler is telling you and until safer times (pun intended) just wrap std::localtime and std::asctime and substitute them for thread safe alternatives and you will be alright.

::: Thanks to Scott Meyers who pointed out details regarding std::put_time and std::strftime when I took his class “An overview of the New C++” in Uppsala last year :::

About these ads

About kjellkod

Software Engineer by trade, (former?) Martial Artist and Champion by strong will and small skill... and a Swede by nationality :)
This entry was posted in C++. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s