static variable in function

Started by
11 comments, last by Oberon_Command 8 years, 3 months ago

I remember a couple years ago someone speaking of putting a singleton variable as a function static instead of a static pointer variable as a private member of the class.

However, since statics in functions aren't constructed until they are first approached, then somehow the code has to prevent it from reconstructing the singleton every single time a user calls GetInstance(). So what happens to the code?

static RenderManager& RenderManager::GetInstance()

{

static RenderManager renderManager;

return renderManager;

}

//Call GetInstance()

//Construct local static variable at some globally defined address

//return instance

//Call GetInstance() again

//---------- what instruction would go here? is it replaced with a "no-op"?

//Return instance

NBA2K, Madden, Maneater, Killing Floor, Sims http://www.pawlowskipinball.com/pinballeternal

Advertisement

Bar& Foo() {
00BC70E0  push        ebp  
00BC70E1  mov         ebp,esp  
00BC70E3  sub         esp,0C0h  
00BC70E9  push        ebx  
00BC70EA  push        esi  
00BC70EB  push        edi  
00BC70EC  lea         edi,[ebp-0C0h]  
00BC70F2  mov         ecx,30h  
00BC70F7  mov         eax,0CCCCCCCCh  
00BC70FC  rep stos    dword ptr es:[edi]  
  static Bar bar;
00BC70FE  mov         eax,dword ptr [_tls_index (0C6A708h)]  
00BC7103  mov         ecx,dword ptr fs:[2Ch]  
00BC710A  mov         edx,dword ptr [ecx+eax*4]  
00BC710D  mov         eax,dword ptr ds:[00C6A1E0h]  
00BC7112  cmp         eax,dword ptr [edx+104h]  //if it's already instantiated
00BC7118  jle         Foo+67h (0BC7147h)  //jump to return
00BC711A  push        0C6A1E0h  
00BC711F  call        __Init_thread_header (0BB7B54h)  
00BC7124  add         esp,4  
00BC7127  cmp         dword ptr ds:[0C6A1E0h],0FFFFFFFFh  
00BC712E  jne         Foo+67h (0BC7147h)  
00BC7130  mov         ecx,0C6A1DCh  
00BC7135  call        Bar::Bar (0BB7A91h)  
00BC713A  push        0C6A1E0h  
00BC713F  call        __Init_thread_footer (0BB88D3h)  
00BC7144  add         esp,4  
  return bar;
00BC7147  mov         eax,0C6A1DCh  
}
00BC714C  pop         edi  
00BC714D  pop         esi  
00BC714E  pop         ebx  
00BC714F  add         esp,0C0h  
00BC7155  cmp         ebp,esp  
00BC7157  call        __RTC_CheckEsp (0BB91BBh)  
00BC715C  mov         esp,ebp  
00BC715E  pop         ebp  
00BC715F  ret  
void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.

I really need to learn x86 assembly...

It is doing a jump on condition that the compare was "less than". What did it actually compare there?

NBA2K, Madden, Maneater, Killing Floor, Sims http://www.pawlowskipinball.com/pinballeternal

Here's my understanding of it (correct me if I'm wrong anywhere):

C6A708h is your EXE/DLL's _tls_index (the stored TlsAlloc return value that the startup code (MSVCRT?) reserved for your EXE/DLL's thread local storage index. It allows EXEs and the DLLs to know where their own dedicated area of the TLS is.)

ecx = fs:[2Ch] is the Win32-specific way to get the thread's TLS pointer.
edx = [ecx+eax*4] is looking up your EXE/DLL-specific area of thread local storage using the _tls_index.
[edx+104h] is accessing a member of the structure it points at. The constant offset is determined at link time - each thread static gets a different constant. The earlier use of _tls_index is what allows this to be constant.

C6A1E0h is the address of a variable indicating whether it has performed initialization yet or not.

I'm not familiar with the __Init_thread_header/footer functions.

C6A1DCh is the address of your actual static variable.
Yes, it is instantiated the first time it is hit.

However, it is still a bad practice generally for many reasons.

It is poor management of object lifetime since you aren't really controlling when it gets created and you do not have any control over when it gets cleaned up.

It is a hidden dependency that cannot be substituted out; there is no way to make another object derived from it for use, no way to make a proxy or fake object for testing, no way to extend the type.

It will likely expose mutable state and mutable data. Some patterns of shared items can work if you have strict policies that keep the data immutable. Said differently, you need to be certain no code changes values at unexpected times. You need strict rules to ensure the values are always what you expect. It is a tradeoff made by many games to have a well-known object but to strictly control what happens with it and when that happens Even with strict controls this is a common source of bugs. Don't make that tradeoff without considering the problems.


Usually a much better alternative if you are still going with a common instance is to have a global pointer to an instance. And instead of an actual concrete type it should usually follow the dependency inversion rule, using an abstract type that includes all the functions you need that is also implemented by the RenderManager class. This lets you still have your easy-to-find object for use everywhere, but also lets you substitute it out for testing or extension, and lets you control exactly when it gets created and destroyed.
To hopefully put this in easier to read terms...

The compiler does the equivalent of this (not valid C++ code):


static bool __renderManagerInitialized = false;
static RenderManager& RenderManager::GetInstance()
{
   //static RenderManager renderManager;
   if (!__renderManagerInitialized)
   {
       new (&renderManager) RenderMananger(); // placement-new if you haven't seen it, creates an object at a pre-allocated memory location
       __renderManagerInitialized;
   }
   return renderManager;
}
Depending on your compiler, it may or may not implement this in a thread-safe manner. C++11 requires thread-safe static variable initialization, but check your compiler to see if it supports it (i.e. MSVC doesn't support this until 2013 or later)

Edit: Frob's points are also very valid - you should avoid singletons wherever possible, and, if you do use them, try to make sure you specifically set up the order in which they are created, usually via static Init/Shutdown functions on the class that manipulate a pointer that the QInstance() can return.

While I agree with you frob, I was merely looking to understand what happens in the code for statics in functions.

C6A1E0h is the address of a variable indicating whether it has performed initialization yet or not.

That's what I was wondering. It incurs a cost of a 'flag' variable indicating it was initialized. That is the only thing I could think it would compare.

Thanks.

NBA2K, Madden, Maneater, Killing Floor, Sims http://www.pawlowskipinball.com/pinballeternal

Seems SmkViper got me before the reply. Make sense.

NBA2K, Madden, Maneater, Killing Floor, Sims http://www.pawlowskipinball.com/pinballeternal

Depending on your compiler, it may or may not implement this in a thread-safe manner. C++11 requires thread-safe static variable initialization, but check your compiler to see if it supports it (i.e. MSVC doesn't support this until 2013 or later)

Edit: Frob's points are also very valid - you should avoid singletons wherever possible, and, if you do use them, try to make sure you specifically set up the order in which they are created, usually via static Init/Shutdown functions on the class that manipulate a pointer that the QInstance() can return.


Just as an addendum to this: the introduction of thread-safe statics introduces a new reason for this pattern to be a bad practice. Consider what happens if RenderManager::GetInstance() is called from somewhere inside of RenderManager's constructor. Then you have a callstack that looks like:

>RenderManager::GetInstance()
>ThingThatNeedsTheRenderer()
>...
>RenderManager::RenderManager()
>RenderManager::GetInstance()

Then, because in some implementations RenderManager::GetInstance() locks a mutex to be sure that only one thread can access __renderManagerInitialized at a time, the second call will see that the singleton is both not constructed and the mutex needed to access it is locked, causing the thread that initially called RenderManager::GetInstance() to deadlock.

Incidentally, this pattern is called a Meyers Singleton.

void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.

This topic is closed to new replies.

Advertisement