C/C++の関数内のstatic変数について


Table of Contents

1 bssセクションに保存される

関数内でstaticをつけて宣言された変数はコンパイル時にbssセクション(静的領域)に確保されます。

.bssセクションに設置された変数は0で初期化されます。

バッファ等の大きい領域を必要とする変数はヒープや静的領域に確保します。

void func(){ static char buffer[4096];}

staticをつけずにスタックに大きなバッファを確保したコードは、実行時にスタックがオーバーフローしてしまいます。

void func(){ char buffer[1 * 1024 * 1024]; /* Stack overflow. */ memcpy(buffer, “hello”, 6); /* SIGSEGV */}int main(){ func(); return 0;}

ただし、staticをつけるとスレッドセーフではなくなるので(staticをつけていない場合はスレッド毎に用意されたスタックを用いる)、変数が大きな領域を必要としない場合はstaticをつけないでおくか、大きな領域を必要とする場合はstaticを付けつつ別途staticなロック機構を用意する必要があります。

 

上のコードの場合はmemcpyをロック機構で保護する必要があります。

2 staticがついたPOD型の初期化

Stackoverflowの投稿によれば、staticがついたPOD型の変数は変数のスコープに入る前に初期化されると規格で規定されています。

GCCでは.init_arrayで初期化するようになっています。

POD型とはコンストラクタを持たない型です。

基本型やコンストラクタを定義していないstructが該当します。

 

以下のコードでbufferは.init_arrayに配置され、プログラム実行時にゼロで初期化されます。

static void func(){ static int buffer[SIZE];}

gcc -Sの結果は以下の通りです。

.LFE1024: .size _GLOBAL__sub_I_main, .-_GLOBAL__sub_I_main .section .init_array,”aw” .align 8 .quad _GLOBAL__sub_I_main .local _ZZL4funcvE6buffer .comm _ZZL4funcvE6buffer,134217728,32 .hidden __dso_handle .ident “GCC: (Ubuntu 5.2.1-22ubuntu2) 5.2.1 20151010″ .section .note.GNU-stack,””,@progbits

以下のコードはbuffer0番目の要素を0で初期化し、残りの要素を0で初期化する意味合いになります。

最近のGCCでは上述のコードと同じ内容になります(0番目の要素を0で初期化し、残りの要素を初期化するコードは生成されません)。

static void func(){ static int buffer[SIZE] = { 0 };}

以下のコードでは.init_arrayに設置されたbufferは、_ZZL4funcvE6bufferを指すobjectとなり、0番目の要素を1で初期化し、残りの要素を0で初期化するコードになります(.long 1と.zero 残りのサイズ)。

なおstaticをつけない場合は0番目の要素を1に設定し、残りの部分はmemsetで0に設定するコードが生成されます。

static void func(){ static int buffer[SIZE] = { 1 };} .LFE1024: .size _GLOBAL__sub_I_main, .-_GLOBAL__sub_I_main .section .init_array,”aw” .align 8 .quad _GLOBAL__sub_I_main .data .align 32 .type _ZZL4funcvE6buffer, @object .size _ZZL4funcvE6buffer, 134217728_ZZL4funcvE6buffer: .long 1 .zero 134217724 .hidden __dso_handle .ident “GCC: (Ubuntu 5.2.1-22ubuntu2) 5.2.1 20151010″ .section .note.GNU-stack,””,@progbits

3 staticがついたPOD型でない型の初期化

staticがついたPOD型でない型は最初の関数実行時に初期化されます。

関数外で定義したstaticな変数とは異なり、関数内で定義したstaticな変数は関数が初めて実行される際に初期化されます。

1. __cxa_guard_acquire2. 初期化したかを確認し、してない場合は初期化する3. __cxa_guard_release

__cxa_guard_acquire/__cxa_guard_releaseはpthread_mutex_tでロックを取る為、スレッドセーフなコードが生成されます。

例えば以下のコードは次のような処理になります。

MyClass *MyClass::singleton(){ static MyClass gMyClass; return &gMyClass;} 1. __cxa_guard_acquire2. MyClass()でgMyClassを初期化3. __cxa_guard_release4. return &gMyClass

__cxa_guard_acquire/__cxa_guard_releaseはあくまで初期化周りのロックとなります。

3.1 シングルトンパターンの実装

先ほどのスレッドセーフなコード生成を利用して、シングルトンパターンを実装できます。

MyClass *MyClass::singleton(){ static MyClass gMyClass; return &gMyClass;}

上のコードでは、関数を呼び出す度に__cxa_guard_acquire/__cxa_guard_releaseと変数初期化確認の処理が実行されます。

関数外に出してやればこれらの処理は実行されません。

static MyClass gMyClass;MyClass *MyClass::singleton(){ return &gMyClass;}

関数外のstaticな変数はmain関数が呼ばれる前に実行される為、main関数以降で初期化した場合には問題になります。

以下のようにstaticな初期化関数と終了化関数をクラスで定義して、singleton関数を使用する前に初期化すると良いでしょう。

static M
yClass *gMyClass = nullptr;MyClass *MyClass::singleton(){ return gMyClass;}void MyClass::initialize(){ gMyClass = new MyClass();}void MyClass::finalize(){ delete gMyClass;}

Android | Linux | SDL - Narrow Escape