5

As you know the <time.h> standard header defines the struct tm and a function called localtime.

Does localtime make a heap allocation?
Or is it allocated in the stack?
It returns a pointer but this could be just a pointer to the stack value, right?

chqrlie
  • 131,814
  • 10
  • 121
  • 189
trianglx
  • 89
  • 5
  • 2
    It returns a pointer to a *static* object. You can see this if you do something like `struct tm *tp1 = localtime(&t1); struct tm *tp1 = localtime(&t2);`. You'll find that `tp1` and `tp2` both point to `t2`'s time; in fact if you print the two pointers using `%p`, you'll see that they're identical. – Steve Summit Jul 03 '22 at 10:44
  • So it doesn't need to be deleted, right? – trianglx Jul 03 '22 at 10:45
  • You mean, you don't need to call `free()` on it? Right. (Standard library functions that return pointers to heap-allocated memory are rare, and the documentation always makes it explicit that they do, and that it's the caller's responsibility to call `free()`.) – Steve Summit Jul 03 '22 at 10:46
  • Oh, well thank you for your answer. I've been thinking about this for a long time. – trianglx Jul 03 '22 at 10:48
  • It's a simple but imperfect solution to the problem of having a function return a pointer to a larger object. Among other things, it's not [thread-safe](https://en.wikipedia.org/wiki/Thread_safety). – Steve Summit Jul 03 '22 at 10:51
  • 2
    POSIX/Linux/C23 has `localtime_r()` which takes a pointer to the `struct tm` to populate as an argument, and Windows has `localtime_s()` that does the same, btw. They should be preferred, especially in multi-threaded programs. With them, the caller is responsible for managing the memory - a local variable or allocated with `malloc()`, whichever makes more sense. – Shawn Jul 03 '22 at 10:52
  • 2
    @SteveSummit At least some implementations keep a copy of `struct tm` per thread, and return a pointer to one belonging to the calling thread. I know MSVC standard library does this. – Igor Tandetnik Jul 03 '22 at 12:34

2 Answers2

3

The relevant part of the C Standard (C18) has this language:

7.27.3 Time conversion functions

Except for the strftime function, these functions each return a pointer to one of two types of static objects: a broken-down time structure or an array of char. Execution of any of the functions that return a pointer to one of these object types may overwrite the information in any object of the same type pointed to by the value returned from any previous call to any of them and the functions are not required to avoid data races with each other. The implementation shall behave as if no other library functions call these functions.

localtime returns a pointer to a static struct tm object, ie: a global object that may or may not be thread local. The contents of this object can be overwritten by a subsequent call to this or another library function. It should not be accessed after the thread in which it the function was called has exited.

The object is not allocated from the heap, You must not call free with this pointer.

The object cannot be allocated with automatic storage as accessing it after the function localtime returns would have undefined behavior.

Instead of localtime, you should use localtime_r, a POSIX function that will be included in the next version of the C Standard (C23):

#include <time.h>
struct tm *localtime_r(const time_t *timer, struct tm *buf);

localtime_r takes a pointer to the destination object, which you can define or allocate as appropriate for your usage.

MSVC might not support this function, but you can define it on its target platforms as a simple macro:

#ifdef _MSC_VER
#define localtime_r(a,b)  localtime_s(a,b)
#endif
chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • `static` means the object has static storage duration, which is distinct from heap allocated objects. It also means that this object's lifetime is decoupled from that of the heap. – Maxim Egorushkin Jul 03 '22 at 19:13
  • 1
    @MaximEgorushkin: I agree, but the text of the Standard does not use `static` in bold face, nor does it mention *static storage class*, it merely says *static object*, which should mean *static storage duration*, which in C17 is different from *thread storage class*. – chqrlie Jul 03 '22 at 20:39
  • 2
    @MaximEgorushkin: C23 changes the wording to something even more vague: *Functions `asctime`, `ctime`, `gmtime`, and `localtime` are the same as their counterparts suffixed with `_r`. these functions use a pointer to an object and return it: one or two broken-down time structures races with each other. Accessing the returned pointer after the thread that called the function that returned it has exited results in undefined behavior.* These functions are really unsafe to use in a threaded application, `localtime_r` must be used instead. – chqrlie Jul 03 '22 at 20:40
  • The bold for static is mine. The static in the standard means not the keyword, but that it has static storage duration, the lifetime of which is the entire program execution time, unlike heap. That function and its standard requirements predate multi-threading and thread local storage. – Maxim Egorushkin Jul 03 '22 at 21:58
  • It appears that even if localtime does return a pointer to static memory, it does call malloc inside! On my Ubuntu localtime calls malloc many times on the first call (when it reads file /etc/localtime) and it continues calling strdup on every call of localtime (you can see source for tzset_internal in tzset.c online). So, if your want to avoid calling malloc for performance or compliance reasons, you should not call localtime. localtime_r, on the other hand, appears to only call malloc on the first call, but I am not sure whether this is guaranteed or not. – jhnlmn May 01 '23 at 23:01
1

localtime returns a pointer to a global variable, it is often shared between all threads:

Return value: pointer to a static internal std::tm object on success, or null pointer otherwise. The structure may be shared between std::gmtime, std::localtime, and std::ctime, and may be overwritten on each invocation.

This function may not be thread-safe.

Modern applications should use localtime_r instead of localtime.

Example: glibc implementation of localtime.

Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
  • "it is shared between all threads:" I find no support for this assertion that is must be shared. If a C or C++ implementation used separate objects/thread - that would be, AFAICT, compliant. – chux - Reinstate Monica Jul 03 '22 at 17:03
  • @chux-ReinstateMonica _pointer to a static internal object_ means there is **one** object. Pointers to this one object must compare equal in all threads, the standard requires that. – Maxim Egorushkin Jul 03 '22 at 17:07
  • Ref appears to be a POSIX one (yet this is a visual C++ question). I find no support for "Pointers to this one object must compare equal in all threads" in the C spec. (Unfortunately the question is tagged C & C++). – chux - Reinstate Monica Jul 03 '22 at 17:15
  • @chux-ReinstateMonica I don't think that claim of mine requires any further support beyond the obvious fact, that if you expect or require it to be thread-specific then you are creating unnecessary problems for yourself. – Maxim Egorushkin Jul 03 '22 at 17:24
  • Agree'd. Also if you expect or require it to be shared between all threads: (as asserted by this answer) then you are creating unnecessary problems for yourself. – chux - Reinstate Monica Jul 03 '22 at 18:36
  • 1
    It's not that it *must be* shared between all threads, but in some implementations, it *may be* shared. i.e. you can't rely on it not being shared (and you can't rely on it being shared, either) – Jeremy Friesner Jul 03 '22 at 19:14