Question

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.

Was it helpful?

Solution

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:

  • Component A calls TlsAlloc and gets assigned slot 1. Slot 1 has never been used, so it contains its initial value of 0.
  • Component A calls TlsSetValue(1, someValue).
  • Component A calls TlsGetValue(1) and it gets someValue back.
  • Component A is finished and calls TlsFree(1).
  • Component B calls TlsAlloc and gets assigned slot 1.
  • Component B calls TlsGetValue(1) and gets someValue back because that is the garbage value left behind by component A.

Therefore, it is up the programmer to ensure that the thread calls TlsSetValue before calling TlsGetValue. Otherwise, your TlsGetValue will 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 calls TlsGetValue(1) it gets zero. This assumes that there is not a bug in Component A where it calls TlsSetValue(1) after TlsFree(1).

OTHER TIPS

Raymond Chen's reasoning made perfect sense, so I accepted his answer yesterday, but today I spotted another key line in the MSDN documentation for TlsAlloc:

If the function succeeds, the return value is a TLS index. The slots for the index are initialized to zero.

If TlsAlloc really does initialize the slots to zero, then you don't have to worry about a garbage value left behind by a previous user of that slot. To verify that TlsAlloc does actually behave this way, I ran the following experiment:

void TlsExperiment() {
  DWORD index1 = ::TlsAlloc();
  assert(index1 != TLS_OUT_OF_INDEXES);
  LPVOID value1 = ::TlsGetValue(index1);
  assert(value1 == 0);  // Nobody else has used this slot yet.
  value1 = reinterpret_cast<LPVOID>(0x1234ABCD);
  ::TlsSetValue(index1, value1);
  assert(value1 == ::TlsGetValue(index1));
  ::TlsFree(index1);

  DWORD index2 = ::TlsAlloc();
  // There's nothing that requires TlsAlloc to give us back the recently freed slot,
  // but it just so happens that it does, which is convenient for our experiment.
  assert(index2 == index1); // If this assertion fails, the experiment is invalid.

  LPVOID value2 = ::TlsGetValue(index2);
  assert(value2 == 0);  // If the TlsAlloc documentation is right, value2 == 0.
                        // If it's wrong, you'd expect value2 == 0x1234ABCD.
}

I ran the experiment on Windows 7, with both 32- and 64-bit test programs, compiled with VS 2010.

The results support the idea that TlsAlloc re-initializes the value to 0. I suppose TlsAlloc might be doing something lame like zeroing out the value just for the current thread, but the documentation explicitly said "slots" (plural), so it seems safe assume that if your thread hasn't yet used a slot, then the value will be 0.

Update: Further experimentation suggests that it's the TlsFree rather than the TlsAlloc that re-initializes the slots to 0. Thus, as Raymond pointed out, there's still a risk that another component called TlsSetValue after freeing a slot. That would be a bug in the other component, but it would affect your code.

I don't see the contradiction. The second page simply tells you that the system initializes all TLS slots to 0 but that beyond some rudimentary bounds checks, has no way of knowing whether a particular TLS index contains valid data (0 could be a perfectly valid set-by-the-user value too!) and that it's up to you to make sure that the index you request is the index you want and that it contains valid data.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top