I found a contradiction in MSDN regarding initial values for thread-local storage. This page says:
When the threads are created, the system allocates an array of LPVOID values for TLS, which are initialized to NULL.
This leads me to believe that if I call TlsGetValue with a valid index from a thread that has never called TlsSetValue for the same index, then I should get a null pointer.
This page, however, says:
It is up to the programmer to ensure … that the thread calls TlsSetValue before calling TlsGetValue.
This suggests that you cannot rely on the value returned from TlsGetValue unless you’re sure it’s been explicitly initialized with TlsSetValue.
Yet the second page simultaneously reinforces the initialized-to-null behavior by also saying:
The data stored in a TLS slot can have a value of 0 because it still has its initial value or because the thread called the TlsSetValue function with 0.
So I’ve got two statements saying that the data is initialized to null (or 0), and one saying that I must initialize it explicitly before reading the value. Experimentally, the values do seem to be initialized to null pointers automatically, but I have no way of knowing if I’m just getting lucky and whether this will always be the case.
I’m trying to avoid using a DLL just to allocate on DLL_THREAD_ATTACH. I’d like to do lazy allocation along the lines of:
LPVOID pMyData = ::TlsGetValue(g_index);
if (pMyData == nullptr) {
pMyData = /* some allocation and initialization*/;
// bail out if allocation or initialization failed
::TlsSetValue(g_index, pMyData);
}
DoSomethingWith(pMyData);
Is this a reliable and safe pattern? Or do I have to explicitly initialize the slot in each thread before I ever try to read it?
UPDATE: The documentation also says that TlsAlloc zeros out the slots for the allocated index. So whether or not a slot has previously been used by another part of the program seems irrelevant.
The documentation is being too helpful when it says that the initial value the system allocates for TLS is zero. The statement is true but not useful.The reason is that applications can free TLS slots by calling
TlsFree, so when you allocate a slot, there is no guarantee that you are the first person ever to be given that slot. Therefore, you don’t know whether the value is the initial 0 assigned by the system or some other junk value assigned by the previous owner of the slot.Consider:
TlsAllocand gets assigned slot 1. Slot 1 has never been used, so it contains its initial value of 0.TlsSetValue(1, someValue).TlsGetValue(1)and it getssomeValueback.TlsFree(1).TlsAllocand gets assigned slot 1.TlsGetValue(1)and getssomeValueback because that is the garbage value left behind by component A.Therefore, it is up the programmer to ensure that the thread calls
TlsSetValuebefore callingTlsGetValue. Otherwise, yourTlsGetValuewill read leftover garbage.The misleading documentation is saying “The default value of leftover garbage is zero,” but that’s not helpful because you have no idea what happened to the slot between the time the system initialized it and it eventually got given to you.
Adrian’s follow-up prompted me to study the situation again, and indeed the kernel zeroes out the slot when Component A calls
TlsFree(1), so that when component B callsTlsGetValue(1)it gets zero. This assumes that there is not a bug in Component A where it callsTlsSetValue(1)afterTlsFree(1).