深入理解函數內靜態局部變量初始化


函數內部的靜態局部變量的初始化是在函數第一次調用時執行; 在之后的調用中不會對其初始化。 在多線程環境下,仍能夠保證靜態局部變量被安全地初始化,並只初始化一次。下面通過代碼來分析一些具體的細節:

void foo() {
    static Bar bar;
    // ...
}

通過觀察 gcc 4.8.3 為上述代碼生成的匯編代碼, 我們可以看到編譯器生成了具有如下語義的代碼:

void foo() {
    if ((guard_for_bar & 0xff) == 0) {
        if (__cxa_guard_acquire(&guard_for_bar)) {
            try {
                Bar::Bar(&bar);
            } catch (...) {
                __cxa_guard_abort(&guard_for_bar);
                throw;
            }
            __cxa_guard_release(&guard_for_bar);
            __cxa_atexit(Bar::~Bar, &bar, &__dso_handle);
        }
    }
    // ...
}

 

雖然 bar 是 foo 的局部變量, 但是編譯器在處理上與全局靜態變量類似, 均存儲在 bss 段 (section), 只是 bar 在匯編語言層面上的符號名稱是對 foo()::bar 的編碼 (mangling),具體細節這里不做過多討論。 guard_for_bar 是一個用來保證線程安全和一次性初始化的整型變量,是編譯器生成的,存儲在 bss 段。它的最低的一個字節被用作相應靜態變量是否已被初始化的標志, 若為 0 表示還未被初始化,否則表示已被初始化。__cxa_guard_acquire 實際上是一個加鎖的過程, 相應的 __cxa_guard_abort 和 __cxa_guard_release 釋放鎖。__cxa_atexit 注冊在調用 exit 時或動態鏈接庫(或共享庫) 被卸載時執行的函數, 這里注冊的是Bar的析構函數。值得一提的是__cxa_atexit可被用來實現atexit, atexit(func) 等價於 __cxa_atexit(func, NULL, NULL) (__cxa_atexit 函數原型: int __cxa_atexit(void (*func) (void *), void * arg, void * dso_handle))。

下面列出 __cxa_guard_acquire、  __cxa_guard_abort 和 __cxa_guard_release 這三個二進制標准接口(Itanium C++ ABI)的一種具體實現的源代碼:

// From : http://www.opensource.apple.com/source/libcppabi/libcppabi-14/src/cxa_guard.cxx
// Headers (omitted)

// Note don't use function local statics to avoid use of cxa functions...
static pthread_mutex_t __guard_mutex;
static pthread_once_t __once_control = PTHREAD_ONCE_INIT;

static void makeRecusiveMutex() // 將 __guard_mutex 初始化為遞歸鎖
{
    pthread_mutexattr_t recursiveMutexAttr;
    pthread_mutexattr_init(&recursiveMutexAttr);
    pthread_mutexattr_settype(&recursiveMutexAttr, PTHREAD_MUTEX_RECURSIVE);
    pthread_mutex_init(&__guard_mutex, &recursiveMutexAttr);
}

__attribute__((noinline))
static pthread_mutex_t* guard_mutex()
{
    pthread_once(&__once_control, &makeRecusiveMutex); // 一次性初始化 __guard_mutex
    return &__guard_mutex;
}

// helper functions for getting/setting flags in guard_object
static bool initializerHasRun(uint64_t* guard_object)
{
    // 取最低字節作為是否已初始化的標志
    return ( *((uint8_t*)guard_object) != 0 );
}

static void setInitializerHasRun(uint64_t* guard_object)
{
    *((uint8_t*)guard_object)  = 1;
}

static bool inUse(uint64_t* guard_object)
{
    // 取次低字節作為 guard_object 是否正在被某個線程使用的標志
    return ( ((uint8_t*)guard_object)[1] != 0 );
}

static void setInUse(uint64_t* guard_object)
{
    ((uint8_t*)guard_object)[1] = 1;
}

static void setNotInUse(uint64_t* guard_object)
{
    ((uint8_t*)guard_object)[1] = 0;
}


//
// Returns 1 if the caller needs to run the initializer and then either
// call __cxa_guard_release() or __cxa_guard_abort().  If zero is returned,
// then the initializer has already been run.  This function blocks
// if another thread is currently running the initializer.  This function
// aborts if called again on the same guard object without an intervening
// call to __cxa_guard_release() or __cxa_guard_abort().
//
int __cxxabiv1::__cxa_guard_acquire(uint64_t* guard_object)
{
    // Double check that the initializer has not already been run
    if ( initializerHasRun(guard_object) ) // 如果對象已被初始化
        return 0;

    // We now need to acquire a lock that allows only one thread
    // to run the initializer.  If a different thread calls
    // __cxa_guard_acquire() with the same guard object, we want 
    // that thread to block until this thread is done running the 
    // initializer and calls __cxa_guard_release().  But if the same
    // thread calls __cxa_guard_acquire() with the same guard object,
    // we want to abort.  
    // To implement this we have one global pthread recursive mutex 
    // shared by all guard objects, but only one at a time.  

    int result = ::pthread_mutex_lock(guard_mutex());
    if ( result != 0 ) {
        abort_message("__cxa_guard_acquire(): pthread_mutex_lock "
                      "failed with %d\n", result);
    }
    // At this point all other threads will block in __cxa_guard_acquire()
    
    // Check if another thread has completed initializer run
    if ( initializerHasRun(guard_object) ) { // 再次判斷, 對象是否已被其他線程初始化
        int result = ::pthread_mutex_unlock(guard_mutex());
        if ( result != 0 ) {
            abort_message("__cxa_guard_acquire(): pthread_mutex_unlock "
                          "failed with %d\n", result);
        }
        return 0;
    }
    
    // The pthread mutex is recursive to allow other lazy initialized
    // function locals to be evaluated during evaluation of this one.
    // But if the same thread can call __cxa_guard_acquire() on the 
    // *same* guard object again, we call abort();
    if ( inUse(guard_object) ) { 
        abort_message("__cxa_guard_acquire(): initializer for function "
                      "local static variable called enclosing function\n");
    }
    
    // mark this guard object as being in use
    setInUse(guard_object);

    // return non-zero to tell caller to run initializer
    return 1;
}



//
// Sets the first byte of the guard_object to a non-zero value.
// Releases any locks acquired by __cxa_guard_acquire().
//
void __cxxabiv1::__cxa_guard_release(uint64_t* guard_object)
{
    // first mark initalizer as having been run, so 
    // other threads won't try to re-run it.
    setInitializerHasRun(guard_object);

    // release global mutex
    int result = ::pthread_mutex_unlock(guard_mutex());
    if ( result != 0 ) {
        abort_message("__cxa_guard_acquire(): pthread_mutex_unlock "
                      "failed with %d\n", result);
    }
}



//
// Releases any locks acquired by __cxa_guard_acquire().
//
void __cxxabiv1::__cxa_guard_abort(uint64_t* guard_object) // 初始化異常時被調用
{
    int result = ::pthread_mutex_unlock(guard_mutex());
    if ( result != 0 ) {
        abort_message("__cxa_guard_abort(): pthread_mutex_unlock "
                      "failed with %d\n", result);
    }
    // now reset state, so possible to try to initialize again
    setNotInUse(guard_object);
}

 最后提供一個很有價值的參考: http://wiki.osdev.org/C%2B%2B


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM