アセンブリからc++のstaticキーワードを見る

16965 ワード

c++のstaticキーワードは、グローバル変数、ローカル変数、クラスメンバーデータを修飾できます(もちろんクラスのメンバー関数もありますが、ここではstaticが変数を修飾する場合のみ説明します).staticがグローバル変数を修飾する場合、単純なグローバル変数と同様に、ライフサイクルはプログラム実行期間全体に存在し、プログラムロード後、最初のプログラム文が実行される前に存在します.ただし、コンパイラはファイルの役割ドメインのみを制限します(すなわち、本明細書でのみアクセスできます).したがってstatic修飾のグローバル変数は、ファイルの役割ドメインのみのグローバル変数に等しい.
static修飾の局所変数は少し特殊で、この変数の可視性(つまり役割ドメイン)は関数の中にあるが、ライフサイクルは確かにプログラムの実行期間全体であり、実際の状況を見てみよう.
c++ソース:
void add(int i) {

    static int sum = 0;

    sum += i;

}



int main() {

    add(5);

}

対応する符号化:
_BSS    SEGMENT

?sum@?1??add@@YAXH@Z@4HA DD 01H DUP (?)            ; sum     

; Function compile flags: /Odtp

_BSS    ENDS

_TEXT    SEGMENT

_i$ = 8                            ; size = 4

?add@@YAXH@Z PROC                    ; add



; 1    : void add(int i) {



    push    ebp

    mov    ebp, esp



; 2    :     static int sum = 0;

; 3    :     sum += i;



    mov    eax, DWORD PTR ?sum@?1??add@@YAXH@Z@4HA; sum       eax

    add    eax, DWORD PTR _i$[ebp];   i  , eax     

    mov    DWORD PTR ?sum@?1??add@@YAXH@Z@4HA, eax;        sum       



; 4    : }



    pop    ebp

    ret    0

?add@@YAXH@Z ENDP                    ; add

_TEXT    ENDS

PUBLIC    _main

; Function compile flags: /Odtp

_TEXT    SEGMENT

_main    PROC



; 6    : int main() {



    push    ebp

    mov    ebp, esp



; 7    :     add(5);



    push    5;   5  

    call    ?add@@YAXH@Z                ;   add  

    add    esp, 4



; 8    : }



    xor    eax, eax

    pop    ebp

    ret    0

_main    ENDP

_TEXT    ENDS

局所静的変数sumは、通常の局所変数のようにスタック空間に割り当てられるのではなく、メモリ内の静的データ領域(_BBSで指定)に割り当てられるため、その宣言期間はスタック空間の割り当てと解放の影響を受けず、プログラム全体の実行期間であるが、彼の可視性(役割ドメイン)は任意に関数内にのみ存在することが分かる.これはコンパイラによって保証されています.(名称粉砕法により実現)
しかし、ローカル静的変数は、一度だけ初期化を実現する方法については、次のコードを参照してください.
C++ソース:
void add(int i) {

    static int sum = i;

    sum++;

}



int main() {

   for (int i = 0; i < 3; i++) {

       add(i);

    }

}

main関数のアセンブリはループの制御とadd関数の呼び出ししか実現していないため、add関数のアセンブリ符号化に重点を置いています.
_TEXT    SEGMENT

_i$ = 8                            ;   i     

?add@@YAXH@Z PROC                    ; add    



; 1    : void add(int i) {



    push    ebp

    mov    ebp, esp



; 2    :     static int sum = i;



    mov    eax, DWORD PTR ?$S1@?1??add@@YAXH@Z@4IA;   ?$S1@?1??add@@YAXH@Z@4IA   (   0) eax    ,          sum          

    and    eax, 1;//          0  1

    jne    SHORT $LN1@add;//          0,    $LN1@add   ,  sum      

    mov    ecx, DWORD PTR ?$S1@?1??add@@YAXH@Z@4IA;//     0      (  sum     ), ?$S1@?1??add@@YAXH@Z@4IA        ecx

    or    ecx, 1;    ecx       1

    mov    DWORD PTR ?$S1@?1??add@@YAXH@Z@4IA, ecx; ecx      ?$S1@?1??add@@YAXH@Z@4IA  

    mov    edx, DWORD PTR _i$[ebp];    i  

    mov    DWORD PTR ?sum@?1??add@@YAXH@Z@4HA, edx;   i    sum

$LN1@add:



; 3    :     sum++;



    mov    eax, DWORD PTR ?sum@?1??add@@YAXH@Z@4HA;  sum  ,     eax

    add    eax, 1;   1  

    mov    DWORD PTR ?sum@?1??add@@YAXH@Z@4HA, eax;     sum



; 4    : }



    pop    ebp

    ret    0

以上の符号化から、局所静的変数が一度だけ初期化されることを保証する原因は、アドレス?$S1@?1??add@@YAXH@Z@4IAの中の値の最低ビットの判断であり、最低ビットが0であれば、まだ初期化されていないことを説明し、まず初期化操作を実行し、さらに1加算操作を実行する.最下位ビットが1の場合、初期化されたことを示す場合は、初期化操作をスキップし、プラス1操作を直接実行します.
ローカル静的変数の初期化値が定数の場合は、次のように異なります.
c++ソース:
void add(int i) {

    static int sum = 2;//       

    sum++;

}



int main() {

   for (int i = 0; i < 3; i++) {

       add(i);

    }

}

次のmainはadd関数のアセンブリコードです.
_DATA    SEGMENT

?sum@?1??add@@YAXH@Z@4HA DD 02H                ;       sum     ,               

; Function compile flags: /Odtp

_DATA    ENDS

_TEXT    SEGMENT

_i$ = 8                            ; size = 4

?add@@YAXH@Z PROC                    ; add



; 1    : void add(int i) {



    push    ebp

    mov    ebp, esp



; 2    :     static int sum = 2;

; 3    :     sum++;



    mov    eax, DWORD PTR ?sum@?1??add@@YAXH@Z@4HA;// sum  

    add    eax, 1; 1  

    mov    DWORD PTR ?sum@?1??add@@YAXH@Z@4HA, eax;  sum  



; 4    : }



    pop    ebp

    ret    0

上からadd関数には、sumが初期化されたかどうかを判断する上での文がないことがわかります.これは,初期値が定数であるため,コンパイラが最適化したため,1つの定数については,複数回初期化しても値は変わらないため,判断する必要はない.
 
static修飾クラスのメンバーデータの場合、原理は静的グローバル変数と同様に、ライフサイクルはプログラム実行期間全体であり、プログラムロード後、最初のプログラム文が実行される前から存在していたが、この変数がクラスに属する特殊な役割ドメインがあるだけだ.
ローカル静的オブジェクトとグローバル静的オブジェクト
ローカル静的オブジェクトがいつ構築されるか、またいつ構築されるかは、c++ソースコードを参照してください.
 
#include <iostream>

using namespace std;

class X {

public:

    int i;

public:

    X(int ii = 0) : i(ii) {

    }

    ~X() {}

};





X getX() {

   static X x;

   return x;

}



int main() {

    X x = getX();

}

コードは、関数getXでローカル静的オブジェクトを定義します.次に、その構造時の符号化を見てみましょう.
 static X x;

00FA13E7  mov         eax,dword ptr [$S1 (0FA9138h)]  ;

00FA13EC  and         eax,1  

00FA13EF  jne         getX+85h (0FA1425h)  ;     0FA1425h   

00FA13F1  mov         eax,dword ptr [$S1 (0FA9138h)]  

00FA13F6  or          eax,1  

00FA13F9  mov         dword ptr [$S1 (0FA9138h)],eax  

00FA13FE  mov         dword ptr [ebp-4],0  

00FA1405  push        0  ;    0,      

00FA1407  mov         ecx,offset x (0FA913Ch)  ;   x        ecx,             

00FA140C  call        X::X (0FA1046h)  ;      

00FA1411  push        offset `getX'::`2'::`dynamic atexit destructor for 'x'' (0FA5610h) ;  atexit           

00FA1416  call        @ILT+100(_atexit) (0FA1069h)  

00FA141B  add         esp,4  

00FA141E  mov         dword ptr [ebp-4],0FFFFFFFFh  

   15:    return x;

00FA1425  mov         eax,dword ptr [ebp+8]  

00FA1428  mov         ecx,dword ptr [x (0FA913Ch)]  

00FA142E  mov         dword ptr [eax],ecx  

00FA1430  mov         edx,dword ptr [ebp-0D4h]  

00FA1436  or          edx,1  

00FA1439  mov         dword ptr [ebp-0D4h],edx  

00FA143F  mov         eax,dword ptr [ebp+8]  

    16: }

符号化から,局所静的変数と同様に局所静的オブジェクトを構築する際にも判断フラグがあり,atexit関数により解析エージェント関数も登録されていることがわかる.プログラムが終了すると、この登録されたプロファイルエージェント関数が呼び出され、プロファイルエージェント関数は真のオブジェクトxのプロファイル関数を呼び出す.
静的グローバルオブジェクトについては、役割ドメインが本ファイルに制限されている以外は、グローバルオブジェクト(『アセンブリから見たc++のグローバルオブジェクトとグローバル変数』)と同じです.