c++ 反汇编 局部静态变量
阅读原文时间:2023年07月09日阅读:2

vs2017下测试

34: for (int i = 0; i < 5; i++)
0029734E C7 45 F8 00 00 00 00 mov dword ptr [ebp-8],0
00297355 EB 09 jmp main+30h (0297360h)
00297357 8B 45 F8 mov eax,dword ptr [ebp-8]
0029735A 83 C0 01 add eax,1
0029735D 89 45 F8 mov dword ptr [ebp-8],eax
00297360 83 7D F8 05 cmp dword ptr [ebp-8],5
00297364 7D 0E jge main+44h (0297374h)
35: {
36: ShowStatic(i);
00297366 8B 45 F8 mov eax,dword ptr [ebp-8]
00297369 50 push eax
0029736A E8 4B C4 FF FF call ShowStatic (02937BAh)
0029736F 83 C4 04 add esp,4
37: }
00297372 EB E3 jmp main+27h (0297357h)

局部静态变量只初始化一次。

void ShowStatic(int nNumber)
{
static int g_snNumber1 = nNumber;
static int g_snNumber2 = nNumber;
printf("%d \r\n", g_snNumber1);
printf("%d \r\n", g_snNumber2);
}

11: void ShowStatic(int nNumber)
12: {
00297160 55 push ebp
00297161 8B EC mov ebp,esp
00297163 81 EC C0 00 00 00 sub esp,0C0h
00297169 53 push ebx
0029716A 56 push esi
0029716B 57 push edi
0029716C 8D BD 40 FF FF FF lea edi,[ebp-0C0h]
00297172 B9 30 00 00 00 mov ecx,30h
00297177 B8 CC CC CC CC mov eax,0CCCCCCCCh
0029717C F3 AB rep stos dword ptr es:[edi]
13: static int g_snNumber1 = nNumber;
0029717E A1 AC 8E 35 00 mov eax,dword ptr [_tls_index (0358EACh)]
00297183 64 8B 0D 2C 00 00 00 mov ecx,dword ptr fs:[2Ch]
0029718A 8B 14 81 mov edx,dword ptr [ecx+eax*4]
0029718D A1 68 8E 35 00 mov eax,dword ptr ds:[00358E68h] //此线程局部静态变量的初始化标志,初始为0,
00297192 3B 82 04 01 00 00 cmp eax,dword ptr [edx+104h] //[edx+104] 即[[ecx+eax*4]+104]-->[[[fs:[2c]+_tls_index]+104],
//tls中存储的全局 局部静态变量初始化计数器,初始值INT_MIN,0x80000000(-2147483647 - 1);
00297198 7E 2B jle ShowStatic+65h (02971C5h) //判断是否已经初始化
0029719A 68 68 8E 35 00 push 358E68h
0029719F E8 43 A8 FF FF call __Init_thread_header (02919E7h) //(将358E68h(此线程局部静态变量初始化标志),如果为0,则表示未初始化 置ffffffff(-1),表示正在初始化)
002971A4 83 C4 04 add esp,4
002971A7 83 3D 68 8E 35 00 FF cmp dword ptr ds:[358E68h],0FFFFFFFFh //判断是否属于正在初始化状态
002971AE 75 15 jne ShowStatic+65h (02971C5h)
002971B0 8B 45 08 mov eax,dword ptr [nNumber]
002971B3 A3 64 8E 35 00 mov dword ptr [g_snNumber1 (0358E64h)],eax //进行初始化
002971B8 68 68 8E 35 00 push 358E68h
002971BD E8 3A B1 FF FF call __Init_thread_footer (02922FCh) //修改tls中存储的全局计数器,以及358E6Eh,即此线程局部静态变量初始化标志,其赋值与全局计数器相同
002971C2 83 C4 04 add esp,4
14: static int g_snNumber2 = nNumber;
002971C5 A1 AC 8E 35 00 mov eax,dword ptr [_tls_index (0358EACh)]
002971CA 64 8B 0D 2C 00 00 00 mov ecx,dword ptr fs:[2Ch]
002971D1 8B 14 81 mov edx,dword ptr [ecx+eax*4]
002971D4 A1 70 8E 35 00 mov eax,dword ptr ds:[00358E70h]
002971D9 3B 82 04 01 00 00 cmp eax,dword ptr [edx+104h]
002971DF 7E 2B jle ShowStatic+0ACh (029720Ch)
14: static int g_snNumber2 = nNumber;
002971E1 68 70 8E 35 00 push 358E70h
002971E6 E8 FC A7 FF FF call __Init_thread_header (02919E7h)
002971EB 83 C4 04 add esp,4
002971EE 83 3D 70 8E 35 00 FF cmp dword ptr ds:[358E70h],0FFFFFFFFh
002971F5 75 15 jne ShowStatic+0ACh (029720Ch)
002971F7 8B 45 08 mov eax,dword ptr [nNumber]
002971FA A3 6C 8E 35 00 mov dword ptr [g_snNumber2 (0358E6Ch)],eax
002971FF 68 70 8E 35 00 push 358E70h
00297204 E8 F3 B0 FF FF call __Init_thread_footer (02922FCh)
00297209 83 C4 04 add esp,4
15: printf("%d \r\n", g_snNumber1);
0029720C A1 64 8E 35 00 mov eax,dword ptr [g_snNumber1 (0358E64h)]
00297211 50 push eax
00297212 68 60 3E 33 00 push offset string "%d \r\n" (0333E60h)
00297217 E8 AE A1 FF FF call _printf (02913CAh)
0029721C 83 C4 08 add esp,8
16: printf("%d \r\n", g_snNumber2);
0029721F A1 6C 8E 35 00 mov eax,dword ptr [g_snNumber2 (0358E6Ch)]
00297224 50 push eax
00297225 68 60 3E 33 00 push offset string "%d \r\n" (0333E60h)
0029722A E8 9B A1 FF FF call _printf (02913CAh)
0029722F 83 C4 08 add esp,8
17: }
00297232 5F pop edi
00297233 5E pop esi
00297234 5B pop ebx
00297235 81 C4 C0 00 00 00 add esp,0C0h
0029723B 3B EC cmp ebp,esp
0029723D E8 54 BC FF FF call __RTC_CheckEsp (0292E96h)
00297242 8B E5 mov esp,ebp
00297244 5D pop ebp
00297245 C3 ret

其中_Init_thread_header 和 _Init_thread_footer 函数是为了保证局部的静态对象的初始化线程安全。

_Init_thread_header

// Control access to the initialization expression. Only one thread may leave
// this function before the variable has completed initialization, this thread
// will perform initialization. All other threads are blocked until the
// initialization completes or fails due to an exception.

//控制对初始化表达式的访问。只有一个线程可以留下

//此函数在变量完成初始化之前,此线程

//将执行初始化。所有其他线程都被阻塞,直到

//初始化完成或由于异常而失败。

extern "C" void __cdecl _Init_thread_header(int* const pOnce) noexcept
{
_Init_thread_lock();

if (\*pOnce == Uninitialized)//int const Uninitialized = 0;

{  
    \*pOnce = BeingInitialized;//int const BeingInitialized = -1;

}  
else  
{  
    while (\*pOnce == BeingInitialized)  
    {  
        // Timeout can be replaced with an infinite wait when XP support is  
        // removed or the XP-based condition variable is sophisticated enough  
        // to guarantee all waiting threads will be woken when the variable is  
        // signalled.  
        \_Init\_thread\_wait(XpTimeout);

        if (\*pOnce == Uninitialized)  
        {  
            \*pOnce = BeingInitialized;  
            \_Init\_thread\_unlock();  
            return;  
        }  
    }  
    \_Init\_thread\_epoch = \_Init\_global\_epoch;  
}

\_Init\_thread\_unlock();  

}

_Init_thread_footer

// Called by the thread that completes initialization of a variable.
// Increment the global and per thread counters, mark the variable as
// initialized, and release waiting threads.

//由完成变量初始化的线程调用。

//递增全局计数器和每线程计数器,将变量标记为

//初始化并释放等待线程。

extern "C" void __cdecl _Init_thread_footer(int* const pOnce) noexcept
{
_Init_thread_lock();
++_Init_global_epoch;
*pOnce = _Init_global_epoch;
_Init_thread_epoch = _Init_global_epoch;
_Init_thread_unlock();
_Init_thread_notify();
}

_Init_thread_header 和 _Init_thread_footer 函数所在源文件:
vs安装目录\VC\Tools\MSVC\14.16.27023\crt\src\vcruntime\thread_safe_statics.cpp

//
// thread_safe_statics.cpp
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// Helper functions used by thread-safe static initialization.
//
#ifdef _M_CEE
#error This file cannot be built as managed
#endif

#include
#include
#include
#include

int const Uninitialized = 0;
int const BeingInitialized = -1;
int const EpochStart = INT_MIN;

// Access to these variables is guarded in the below functions. They may only
// be modified while the lock is held. _Tss_epoch is readable from user
// code and is read without taking the lock.
extern "C"
{
int _Init_global_epoch = EpochStart;
__declspec(thread) int _Init_thread_epoch = EpochStart;
}

static CRITICAL_SECTION _Tss_mutex;
static CONDITION_VARIABLE _Tss_cv;
static HANDLE _Tss_event;

static decltype(SleepConditionVariableCS)* encoded_sleep_condition_variable_cs;
static decltype(WakeAllConditionVariable)* encoded_wake_all_condition_variable;

// Initializer for synchronization data structures.
// On Vista or newer, the native CONDITION_VARIABLE type is used. On XP, we use a simple
// Windows event. This is not safe to use as a complete condition variable, but for the purposes
// of this feature the event is sufficient but not optimal. See the code in _Tss_wait
// below.
//
// For Windows OS components: The OS supports APISets downlevel to Windows 7,
// and OS components that run downlevel to Windows 7 may build against APISets.
// However, these components cannot use CONDITION_VARIABLE directly because it
// is not available via APISets until Windows 8. Thus, for Windows OS components,
// we use the "ancient" code path and first try the APISet and then fall back to
// kernel32.dll.
// The helper __scrt_is_event_api_used signals the usage of the event API for the
// rest of the code (allows it to be hardcoded to false when guaranteed to not be used).
#if defined _SCRT_ENCLAVE_BUILD || defined _CRT_APP || \
(!defined _CRT_WINDOWS && (defined _ONECORE || defined _KERNELX || defined _M_ARM || defined _M_ARM64))
static void __cdecl __scrt_initialize_thread_safe_statics_platform_specific() noexcept
{
// This can only fail due to invalid parameters (flags) so ignoring error is ok.
InitializeCriticalSectionEx(&_Tss_mutex, 4000, 0);

    InitializeConditionVariable(&\_Tss\_cv);

    encoded\_sleep\_condition\_variable\_cs = \_\_crt\_fast\_encode\_pointer(&SleepConditionVariableCS);  
    encoded\_wake\_all\_condition\_variable = \_\_crt\_fast\_encode\_pointer(&WakeAllConditionVariable);  
}

constexpr bool \_\_scrt\_is\_event\_api\_used(HANDLE) { return false; }  

#else // ^^^ Modern Platforms ^^^ // vvv Ancient Platforms vvv //
static void __cdecl __scrt_initialize_thread_safe_statics_platform_specific() noexcept
{
// This can fail pre-Vista and that is ignored.
InitializeCriticalSectionAndSpinCount(&_Tss_mutex, 4000);

    // CONDITION\_VARIABLE is available via this APISet starting on Windows 8.  
    HMODULE kernel\_dll = GetModuleHandleW(L"api-ms-win-core-synch-l1-2-0.dll");  
    if (kernel\_dll == nullptr)  
    {  
        kernel\_dll = GetModuleHandleW(L"kernel32.dll");  
    }

    if (kernel\_dll == nullptr)  
    {  
        \_\_scrt\_fastfail(FAST\_FAIL\_FATAL\_APP\_EXIT);  
    }

    #define GET\_PROC\_ADDRESS(m, f) reinterpret\_cast<decltype(f)\*>(GetProcAddress(m, \_CRT\_STRINGIZE(f)))

    auto const initialize\_condition\_variable = GET\_PROC\_ADDRESS(kernel\_dll, InitializeConditionVariable);  
    auto const sleep\_condition\_variable\_cs   = GET\_PROC\_ADDRESS(kernel\_dll, SleepConditionVariableCS);  
    auto const wake\_all\_condition\_variable   = GET\_PROC\_ADDRESS(kernel\_dll, WakeAllConditionVariable);

    #undef GET\_PROC\_ADDRESS

    if (initialize\_condition\_variable && sleep\_condition\_variable\_cs && wake\_all\_condition\_variable)  
    {  
        \_Tss\_event = nullptr;  
        initialize\_condition\_variable(&\_Tss\_cv);

        encoded\_sleep\_condition\_variable\_cs = \_\_crt\_fast\_encode\_pointer(sleep\_condition\_variable\_cs);  
        encoded\_wake\_all\_condition\_variable = \_\_crt\_fast\_encode\_pointer(wake\_all\_condition\_variable);  
    }  
    else  
    {  
        \_Tss\_event = CreateEventW(NULL, TRUE, FALSE, NULL);  
        if (\_Tss\_event == nullptr)  
        {  
            \_\_scrt\_fastfail(FAST\_FAIL\_FATAL\_APP\_EXIT);  
        }  
    }  
}

constexpr bool \_\_scrt\_is\_event\_api\_used(HANDLE const \_Event) { return \_Event != nullptr; }  

#endif // Ancient Platforms

// Terminator for synchronization data structures.
static void __cdecl __scrt_uninitialize_thread_safe_statics() noexcept
{
DeleteCriticalSection(&_Tss_mutex);
if (__scrt_is_event_api_used(_Tss_event))
{
CloseHandle(_Tss_event);
}
}

// Initializer for synchronization data structures.
static int __cdecl __scrt_initialize_thread_safe_statics() noexcept
{
__scrt_initialize_thread_safe_statics_platform_specific();

// If CRT initialization was skipped then we should initialize the atexit tables.  
// This will only be needed when using a managed DLL with /NOENTRY specified.  
if (!\_\_scrt\_initialize\_onexit\_tables(\_\_scrt\_module\_type::dll))  
{  
    \_\_scrt\_fastfail(FAST\_FAIL\_FATAL\_APP\_EXIT);  
}  
atexit(\_\_scrt\_uninitialize\_thread\_safe\_statics);  
return 0;  

}

_CRTALLOC(".CRT$XIC") static _PIFV __scrt_initialize_tss_var = __scrt_initialize_thread_safe_statics;

// Helper functions for accessing the mutex and condition variable. Can be replaced with
// more suitable data structures provided by the CRT, preferably ones that use the most
// efficient synchronization primitives available on the platform.
extern "C" void __cdecl _Init_thread_lock()
{
EnterCriticalSection(&_Tss_mutex);
}

extern "C" void __cdecl _Init_thread_unlock()
{
LeaveCriticalSection(&_Tss_mutex);
}

// Wait on the condition variable. In the XP implementation using only a Windows event
// we can't guarantee that we'll ever actually receive the notification signal, so we
// must use a non-infinite timeout. This is not optimal: we may wake up early if the
// initializer is long-running, or we may miss the signal and not wake up until the
// timeout expires. The signal may be missed because the sleeping threads may be
// stolen by the kernel to service an APC, or due to the race condition between the
// unlock call and the WaitForSingleObject call.
extern "C" void __cdecl _Init_thread_wait(DWORD const timeout)
{
if (!__scrt_is_event_api_used(_Tss_event))
{
__crt_fast_decode_pointer(encoded_sleep_condition_variable_cs)(&_Tss_cv, &_Tss_mutex, timeout);
return;
}

\_ASSERT(timeout != INFINITE);  
\_Init\_thread\_unlock();  
WaitForSingleObjectEx(\_Tss\_event, timeout, FALSE);  
\_Init\_thread\_lock();  

}

extern "C" void __cdecl _Init_thread_notify()
{
if (!__scrt_is_event_api_used(_Tss_event))
{
__crt_fast_decode_pointer(encoded_wake_all_condition_variable)(&_Tss_cv);
}
else
{
SetEvent(_Tss_event);
ResetEvent(_Tss_event);
}
}

DWORD const XpTimeout = 100; // ms

// Control access to the initialization expression. Only one thread may leave
// this function before the variable has completed initialization, this thread
// will perform initialization. All other threads are blocked until the
// initialization completes or fails due to an exception.
extern "C" void __cdecl _Init_thread_header(int* const pOnce) noexcept
{
_Init_thread_lock();

if (\*pOnce == Uninitialized)  
{  
    \*pOnce = BeingInitialized;  
}  
else  
{  
    while (\*pOnce == BeingInitialized)  
    {  
        // Timeout can be replaced with an infinite wait when XP support is  
        // removed or the XP-based condition variable is sophisticated enough  
        // to guarantee all waiting threads will be woken when the variable is  
        // signalled.  
        \_Init\_thread\_wait(XpTimeout);

        if (\*pOnce == Uninitialized)  
        {  
            \*pOnce = BeingInitialized;  
            \_Init\_thread\_unlock();  
            return;  
        }  
    }  
    \_Init\_thread\_epoch = \_Init\_global\_epoch;  
}

\_Init\_thread\_unlock();  

}

// Abort processing of the initializer due to an exception. Reset the state
// to uninitialized and release waiting threads (one of which will take over
// initialization, any remaining will again sleep).
extern "C" void __cdecl _Init_thread_abort(int* const pOnce) noexcept
{
_Init_thread_lock();
*pOnce = Uninitialized;
_Init_thread_unlock();
_Init_thread_notify();
}

// Called by the thread that completes initialization of a variable.
// Increment the global and per thread counters, mark the variable as
// initialized, and release waiting threads.
extern "C" void __cdecl _Init_thread_footer(int* const pOnce) noexcept
{
_Init_thread_lock();
++_Init_global_epoch;
*pOnce = _Init_global_epoch;
_Init_thread_epoch = _Init_global_epoch;
_Init_thread_unlock();
_Init_thread_notify();
}

参考链接:

C语言变量及其生命周期

局部静态变量只能初始化一次?它是怎么实现的