1

For years I've been using std::put_time in a function to write a timestamp to a stream. All my logging statements go through this. It has always worked perfectly:

inline std::ostream& logTime(std::ostream& stm,  
                             const std::chrono::system_clock::time_point& tp)
{
    auto time = std::chrono::system_clock::to_time_t(tp);
    tm tmVal = {0};
    localtime_x(&time, &tmVal);
    return stm << std::put_time(&tmVal, "%H:%M:%S"); // Print standard date&time
}

Today when I clicked "close" on my application, it crashed here, right on the use of put_time. (An object in one of my DLLs was in its destructor and logged a statement). The call stack showed an access violation deep within the guts of put_time. Visual studio told me that "this" was some clearly invalid value.

I examined the contents of the tm structure being handed to put_time and it was clearly valid. So was the stream object. In fact, the object being destroyed was valid too. And the DLL was still loaded.

After some fiddling, in desperation, I tried changed the routine to do a crude approximation of what I wanted without using put_time by manually dumping the digits to the stream. And now it works, no crashes. But I don't understand what I did wrong.

Here is what I'm doing now:

inline std::ostream& logTime(std::ostream& stm,  
                             const std::chrono::system_clock::time_point& tp)
{
    auto time = std::chrono::system_clock::to_time_t(tp);
    tm tmVal = {0};
    localtime_x(&time, &tmVal);
    if (tmVal.tm_hour < 10)
        stm << "0";

    stm << tmVal.tm_hour; 

    stm << ':';
    if (tmVal.tm_min < 10)
        stm << "0";

    stm << tmVal.tm_min;

    stm << ':';
    if (tmVal.tm_sec < 10)
        stm << '0';

    stm << tmVal.tm_sec;
    return stm;

//  This is causing access violations on shutdown.  DOn't know why
//    return stm << std::put_time(&tmVal, "%H:%M:%S"); // Print standard date&time
}

So I have a "fix" but I am left to wonder. Why would this work while put_time causes a crash? It's a global function, all of its inputs were valid and it otherwise works well. Is this just Microsoft's CRT maybe storing some context that becomes invalid while shutting down?

(I'm using Visual Studio 16.9, with full C++17 compatibility enabled, if it matters)

Joe
  • 5,394
  • 3
  • 23
  • 54
  • 1
    Probably a variation of [the static order initialization fiasco](https://stackoverflow.com/questions/29822181/prevent-static-initialization-order-fiasco-c), but on destruction, not initialization. – PaulMcKenzie May 20 '21 at 15:57
  • Then I suppose I need to try to create a test example of this and submit it to Microsoft as a bug report.. That's kind of scary when their own CRT doesn't pay close attention to static construction order – Joe May 20 '21 at 17:49
  • I honestly don't think it is an issue with the CRT, but with your expectation that global objects have a deterministic destructor order. If those global objects are not in the same translation unit, there is no guarantee which object(s) will be destroyed before (or after) the other. If you really and truly must use global objects like this, then the safer thing is to dynamically allocate them (using `new`) and never call `delete`, thus the destructor will not be invoked. Yes, you will get a memory leak, but it will just be an annoyance since it will only appear at app shutdown. – PaulMcKenzie May 20 '21 at 23:25
  • The system probably *did* run some destructor code for whatever IO stream `stm` references. C++ static destruction is an unhappy place best avoided. – Zan Lynx May 21 '21 at 00:04
  • I don't think you're taking my point. Whatever object of mine is being destroyed, it is, in fact, being destroyed at a valid time. All of the CRT functions, including put_time, should be available at that point. If the current implementation of the CRT's put_time function depends upon Microsoft's internal usage of static objects and destruction order, that is a bug. The IOStream in question is std::cout. Since I am using the DLL version of the CRT, std::cout is most definitely still alive and kicking in the main, .EXE module. Because it works when I avoid put_time – Joe May 21 '21 at 04:04
  • 1
    I suggest you could post the issue to the [Developer Community](https://developercommunity.visualstudio.com/spaces/8/index.html) for better help. – Jeaninez - MSFT May 21 '21 at 05:32

0 Answers0