Fog Creek Software
Discussion Board

C library subroutine localtime()

It returns a tm*, questions are :

1. does the memory by this pointer need to be freed by
    calling program ?

2. is it thread-save ?

Thanks a lot.

Li Hu
Friday, March 5, 2004

No, you don't have to free it.

Friday, March 5, 2004

In Windows, it should be if you use the MT version of the c library.

Friday, March 5, 2004

From the linux manpages on localtime():

1.  The struct tm is an internal structure.  Don't free it.

2.  It is _not_ thread-safe in general - nor is anything
    that returns a pointer to an internal structure.

Friday, March 5, 2004

Depends if your libraries support multithreading. They should support it. It's not terribly hard.

To LiHu: if you have problems with calling c library functions from different threads, first ensure you are calling the correct function to start a new thread. The system-level call won't know about the C runtime library, and so won't set up a new set of globals for the new thread. This can cause problems. There should be a C library equivalent (_beginthread under VC++, I believe, as opposed to CreateThread Win32 call; I suspect it is similar under Unix) that will do the relevant magic.

Insert half smiley here.
Friday, March 5, 2004

If the library supports multithreading then two concurrent calls to localtime() shouldn't crash the library.

However, if one thread calls localtime() and gets a pointer to the struct, then I don't see what could prevent another thread from calling localtime() before or during the time when the first thread is dereferencing the pointer (reading the data via the pointer returned by the first call).

So I would say that it is not thread-safe. Implement a global function like this ...

tm safegetlocaltime()
  //enter critical section
  tm rc = *localtime();
  //leave critical section
  return rc

... and verify or hope that yours is the only function in the process that calls local time ... in which case safelocaltime would be thread safe.

Christopher Wells
Friday, March 5, 2004

The VC++ runtime library supports thread-local allocation of 'global' Standard C runtime variables.  This includes the global tm structure used by the time functions.  If you look at the source to localtime, you'll see it gets a tm* from an internal call to gmtime.  If you look at the source to gmtime, you'll see a section like this (GMTIME.C):

#ifdef _MT

        REG2 struct tm *ptb;            /* will point to gmtime buffer */
        _ptiddata ptd = _getptd();

        /* Use per thread buffer area (malloc space, if necessary) */

        if ( (ptd->_gmtimebuf != NULL) || ((ptd->_gmtimebuf =
            _malloc_crt(sizeof(struct tm))) != NULL) )
                ptb = ptd->_gmtimebuf;
                ptb = &tb;      /* malloc error: use static buffer */

That _ptiddata structure is the key.  It's actually a pointer to a _tiddata structure representing the global variables of the Standard C library (in MTDLL.H):

/* Structure for each thread's data */

struct _tiddata {
        unsigned long  _tid;      /* thread ID */

        unsigned long  _thandle;  /* thread handle */

        int    _terrno;            /* errno value */

If you look at the code for the _getptd function, which returns a pointer to this structure, you'll see that it uses the Win32 functions TlsGetValue and TlsSetValue to associate allocated _tiddata structures with the current thread (TIDTABLE.C):

_ptiddata __cdecl _getptd (
        _ptiddata ptd;
        DWORD  TL_LastError;

        TL_LastError = GetLastError();
        if ( (ptd = TlsGetValue(__tlsindex)) == NULL ) {
            * no per-thread data structure for this thread. try to create
            * one.
            if ( ((ptd = _calloc_crt(1, sizeof(struct _tiddata))) != NULL) &&
                TlsSetValue(__tlsindex, (LPVOID)ptd) ) {

So, the short version is that the function is thread-safe.  Your tm* in thread A won't be the same tm* as in thread B.  Of course, this sort of thing is important to know if you rely on errno and so on.  You also ought to keep in mind that _beginthread and _endthread have to be used instead of CreateThread so that this structure gets freed (though it won't be allocated at all unless you use one of the Standard C runtime functions).

I hope this helped. :)

Friday, March 5, 2004

> I hope this helped. :)


Christopher Wells
Friday, March 5, 2004

Under linux there is localtime_r, which is the reentrant version of that call.

Oren Miller
Friday, March 5, 2004

*  Recent Topics

*  Fog Creek Home