アセンブリからc++のstaticキーワードを見る
16965 ワード
c++のstaticキーワードは、グローバル変数、ローカル変数、クラスメンバーデータを修飾できます(もちろんクラスのメンバー関数もありますが、ここではstaticが変数を修飾する場合のみ説明します).staticがグローバル変数を修飾する場合、単純なグローバル変数と同様に、ライフサイクルはプログラム実行期間全体に存在し、プログラムロード後、最初のプログラム文が実行される前に存在します.ただし、コンパイラはファイルの役割ドメインのみを制限します(すなわち、本明細書でのみアクセスできます).したがってstatic修飾のグローバル変数は、ファイルの役割ドメインのみのグローバル変数に等しい.
static修飾の局所変数は少し特殊で、この変数の可視性(つまり役割ドメイン)は関数の中にあるが、ライフサイクルは確かにプログラムの実行期間全体であり、実際の状況を見てみよう.
c++ソース:
対応する符号化:
局所静的変数sumは、通常の局所変数のようにスタック空間に割り当てられるのではなく、メモリ内の静的データ領域(_BBSで指定)に割り当てられるため、その宣言期間はスタック空間の割り当てと解放の影響を受けず、プログラム全体の実行期間であるが、彼の可視性(役割ドメイン)は任意に関数内にのみ存在することが分かる.これはコンパイラによって保証されています.(名称粉砕法により実現)
しかし、ローカル静的変数は、一度だけ初期化を実現する方法については、次のコードを参照してください.
C++ソース:
main関数のアセンブリはループの制御とadd関数の呼び出ししか実現していないため、add関数のアセンブリ符号化に重点を置いています.
以上の符号化から、局所静的変数が一度だけ初期化されることを保証する原因は、アドレス?$S1@?1??add@@YAXH@Z@4IAの中の値の最低ビットの判断であり、最低ビットが0であれば、まだ初期化されていないことを説明し、まず初期化操作を実行し、さらに1加算操作を実行する.最下位ビットが1の場合、初期化されたことを示す場合は、初期化操作をスキップし、プラス1操作を直接実行します.
ローカル静的変数の初期化値が定数の場合は、次のように異なります.
c++ソース:
次のmainはadd関数のアセンブリコードです.
上からadd関数には、sumが初期化されたかどうかを判断する上での文がないことがわかります.これは,初期値が定数であるため,コンパイラが最適化したため,1つの定数については,複数回初期化しても値は変わらないため,判断する必要はない.
static修飾クラスのメンバーデータの場合、原理は静的グローバル変数と同様に、ライフサイクルはプログラム実行期間全体であり、プログラムロード後、最初のプログラム文が実行される前から存在していたが、この変数がクラスに属する特殊な役割ドメインがあるだけだ.
ローカル静的オブジェクトとグローバル静的オブジェクト
ローカル静的オブジェクトがいつ構築されるか、またいつ構築されるかは、c++ソースコードを参照してください.
コードは、関数getXでローカル静的オブジェクトを定義します.次に、その構造時の符号化を見てみましょう.
符号化から,局所静的変数と同様に局所静的オブジェクトを構築する際にも判断フラグがあり,atexit関数により解析エージェント関数も登録されていることがわかる.プログラムが終了すると、この登録されたプロファイルエージェント関数が呼び出され、プロファイルエージェント関数は真のオブジェクトxのプロファイル関数を呼び出す.
静的グローバルオブジェクトについては、役割ドメインが本ファイルに制限されている以外は、グローバルオブジェクト(『アセンブリから見たc++のグローバルオブジェクトとグローバル変数』)と同じです.
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++のグローバルオブジェクトとグローバル変数』)と同じです.