さんじゅつろんりビットえんざん
16245 ワード
アルゴリズム結果オーバーフロー:
シンボルレス数がオーバーフローすると最大数から最小数に変わります
シンボル数オーバーフローによりシンボルビットが変更されます
上のコードはデッドサイクルのように見えますが、実はそうではありません.主な原因はiが符号付き整数で、iの最大値は0 x 7 FFFFFFFで実行され、1を加えると最終的に0 x 800000になります.これは最高位が1で、負数なので、飛び出します.
オーバーフローは、データがキャリーされた後にデータの保存範囲を超えたためです.
キャリー:シンボル数が記憶範囲を超えていないことをキャリーと呼びます.符号ビットがないため、データを破壊することはなく、より多く出た1ビットデータはキャリーフラグビットCFに保存され、データはキャリーを生成し、キャリー後CFに保存されるだけである.CFで確認できますし、キャリーの有無も判断できます
オーバーフロー:シンボル数が記憶範囲を超えていることをオーバーフローといい、データがキャリーされるため、シンボル数のある最上位-シンボルビットが破壊される.オーバーフローはシンボル数のみです.OFを確認し、オーバーフローしていないか確認できます.OF判定は,計算に関与する数値記号が一致し,計算結果記号が異なる場合にはOFが成立すると判定する.
他の操作命令によりオーバーフローやキャリーが発生する場合もあります
自己増加と自己減少:
みんなはすべて自増と自減演算を熟知して、同じく法則を知っています:前置と後置、つまり先に自増後演算と先演算後自増
コードで見てみましょう
ソース:
アセンブリコード:
アセンブリレベルのコード実装は、自己増加と自己減少の実装を明確に示しています.
リレーショナル演算と論理演算:
両者の関係比較は種々のタイプのジャンプにより実現でき,比較結果に影響されるフラグビットに基づいて対応する条件ジャンプ命令を選択できる.
ジャンプ命令はみんなよく知っているから、くどくどしないでください.
エクスプレッションショート&:
ソース:
アセンブリコード分析:
主に短絡状況を見る:第1段のコード分析から&&前の式が再帰的に飛び出す条件であることがわかる
これはまさに短絡現象を利用してコードを精錬させたものである.
演算子のショートカットを見てみましょう.
ソースコードは基本的に同じで、アセンブリ対照コードを直接見ます.
比較すると,異なる論理演算子を用いたが,同じアセンブリコードを生成し,それらの機能が完全に同じであることを示した.
条件式:
三目表現はよく知られていませんが、Cの中には一つしかありません.
式1?式2:式3
条件式には4つの変換スキームがあります.
>シナリオ1:式1は単純な比較であり、式2と式3の差は1に等しい
>シナリオ2:式1は単純な比較であり、式2と式3の差は1より大きい
>シナリオ3:式1は複雑な比較であり、式2と式3の差は1より大きい
>シナリオ4:式2と式3は変数であり、最適化されていない
例:
シナリオ1:
ソース:
アセンブリコード:
しかしsetneも1の差のバランスにしか使えず、1より大きいと無力になります
シナリオ2:
ソース:
アセンブリコード:
arg=5という等値比較では,VC++は減算と補正演算を用いて真値か否かを判断する.
argcが5に等しくない限り、sub命令を実行した後、eaxの値は0ではない.
そしてneg命令によりeaxの符号ビットが変化し,CFが1となる.
次に、ビット減算eax=eax−eax−CFを実行する.CFが1の場合、eaxの値は0 xFFFFFFFFFF、そうでない場合は0
eaxと6を使用してビットと演算を行った後、eaxの数値が元の値-1であれば、解果は6、プラス4は数値10に等しい.
そうでない場合argc==5、eaxは常に0で、4を加えて数値4になります.
ただし、式1が1区間である場合は、これではだめです
シナリオ3:
ソース:
アセンブリコード分析:
上はもうはっきり説明した.
ただし、式2または式3の値が未知数の場合、従来の方法では最適化できません.
コンパイラは通常の文の流れに基づいて比較し、判断します.
シナリオ4:
ソース:
アセンブリ言語解析:
ビット演算
<<左シフト演算,最高位左シフトCF,最低位補零
>>右シフト演算、最高位不変、最低位右シフトCF中
コードがどのように実装されているかを見てみましょう.
ソース:
アセンブリコード分析:
上の逆アセンブリコードは比較的簡単で、記号のあるビット演算です.
次の記号のないものは少し違います.
ソース:
アセンブリコード分析:
シンボルレス数がオーバーフローすると最大数から最小数に変わります
シンボル数オーバーフローによりシンボルビットが変更されます
#include
using namespace std;
int main()
{
for(int i = 1; i > 0 ; i += 1)
{
printf("%d \r
",i);
}
return 0;
}
上のコードはデッドサイクルのように見えますが、実はそうではありません.主な原因はiが符号付き整数で、iの最大値は0 x 7 FFFFFFFで実行され、1を加えると最終的に0 x 800000になります.これは最高位が1で、負数なので、飛び出します.
オーバーフローは、データがキャリーされた後にデータの保存範囲を超えたためです.
キャリー:シンボル数が記憶範囲を超えていないことをキャリーと呼びます.符号ビットがないため、データを破壊することはなく、より多く出た1ビットデータはキャリーフラグビットCFに保存され、データはキャリーを生成し、キャリー後CFに保存されるだけである.CFで確認できますし、キャリーの有無も判断できます
オーバーフロー:シンボル数が記憶範囲を超えていることをオーバーフローといい、データがキャリーされるため、シンボル数のある最上位-シンボルビットが破壊される.オーバーフローはシンボル数のみです.OFを確認し、オーバーフローしていないか確認できます.OF判定は,計算に関与する数値記号が一致し,計算結果記号が異なる場合にはOFが成立すると判定する.
他の操作命令によりオーバーフローやキャリーが発生する場合もあります
自己増加と自己減少:
みんなはすべて自増と自減演算を熟知して、同じく法則を知っています:前置と後置、つまり先に自増後演算と先演算後自増
コードで見てみましょう
ソース:
#include
using namespace std;
int main(int argc,char *argv[])
{
int nVarOne = argc;
int nVarTwo = argc;
nVarTwo = 5 + (nVarOne ++);
nVarTwo = 5 + (++ nVarOne);
nVarOne = 5 + (nVarTwo --);
nVarOne = 5 + (-- nVarTwo);
return 0;
}
アセンブリコード:
1: #include
2:
3: using namespace std;
4:
5: int main(int argc,char *argv[])
6: {
00401250 push ebp
00401251 mov ebp,esp
00401253 sub esp,48h
00401256 push ebx
00401257 push esi
00401258 push edi
00401259 lea edi,[ebp-48h]
0040125C mov ecx,12h
00401261 mov eax,0CCCCCCCCh
00401266 rep stos dword ptr [edi]
7: int nVarOne = argc;
00401268 mov eax,dword ptr [ebp+8]
0040126B mov dword ptr [ebp-4],eax
8: int nVarTwo = argc;
0040126E mov ecx,dword ptr [ebp+8]
00401271 mov dword ptr [ebp-8],ecx
9:
10: nVarTwo = 5 + (nVarOne ++);
00401274 mov edx,dword ptr [ebp-4]
00401277 add edx,5
0040127A mov dword ptr [ebp-8],edx
0040127D mov eax,dword ptr [ebp-4]
00401280 add eax,1
00401283 mov dword ptr [ebp-4],eax
11: nVarTwo = 5 + (++ nVarOne);
00401286 mov ecx,dword ptr [ebp-4]
00401289 add ecx,1
0040128C mov dword ptr [ebp-4],ecx
0040128F mov edx,dword ptr [ebp-4]
00401292 add edx,5
00401295 mov dword ptr [ebp-8],edx
12:
13: nVarOne = 5 + (nVarTwo --);
00401298 mov eax,dword ptr [ebp-8]
0040129B add eax,5
0040129E mov dword ptr [ebp-4],eax
004012A1 mov ecx,dword ptr [ebp-8]
004012A4 sub ecx,1
004012A7 mov dword ptr [ebp-8],ecx
14: nVarOne = 5 + (-- nVarTwo);
004012AA mov edx,dword ptr [ebp-8]
004012AD sub edx,1
004012B0 mov dword ptr [ebp-8],edx
004012B3 mov eax,dword ptr [ebp-8]
004012B6 add eax,5
004012B9 mov dword ptr [ebp-4],eax
15:
16: return 0;
004012BC xor eax,eax
17: }
004012BE pop edi
004012BF pop esi
004012C0 pop ebx
004012C1 mov esp,ebp
004012C3 pop ebp
004012C4 ret
アセンブリレベルのコード実装は、自己増加と自己減少の実装を明確に示しています.
リレーショナル演算と論理演算:
両者の関係比較は種々のタイプのジャンプにより実現でき,比較結果に影響されるフラグビットに基づいて対応する条件ジャンプ命令を選択できる.
ジャンプ命令はみんなよく知っているから、くどくどしないでください.
エクスプレッションショート&:
ソース:
#include
using namespace std;
int Accumulation(int nNumber)
{
nNumber && (nNumber += Accumulation(nNumber - 1));
return nNumber;
}
int main(int argc,char *argv[])
{
Accumulation(12);
return 0;
}
アセンブリコード分析:
1: #include
2:
3: using namespace std;
4:
5: int Accumulation(int nNumber)
6: {
00401250 push ebp
00401251 mov ebp,esp
00401253 sub esp,40h
00401256 push ebx
00401257 push esi
00401258 push edi
00401259 lea edi,[ebp-40h]
0040125C mov ecx,10h
00401261 mov eax,0CCCCCCCCh
00401266 rep stos dword ptr [edi]
7: nNumber && (nNumber += Accumulation(nNumber - 1));
00401268 cmp dword ptr [ebp+8],0
0040126C je Accumulation+35h (00401285)
0040126E mov eax,dword ptr [ebp+8]
00401271 sub eax,1
00401274 push eax
00401275 call @ILT+10(Accumulation) (0040100f)
0040127A add esp,4
0040127D mov ecx,dword ptr [ebp+8]
00401280 add ecx,eax
00401282 mov dword ptr [ebp+8],ecx
8: return nNumber;
00401285 mov eax,dword ptr [ebp+8]
9: }
00401288 pop edi
00401289 pop esi
0040128A pop ebx
0040128B add esp,40h
0040128E cmp ebp,esp
00401290 call __chkesp (004081b0)
00401295 mov esp,ebp
00401297 pop ebp
00401298 ret
10:
11: int main(int argc,char *argv[])
12: {
004012B0 push ebp
004012B1 mov ebp,esp
004012B3 sub esp,40h
004012B6 push ebx
004012B7 push esi
004012B8 push edi
004012B9 lea edi,[ebp-40h]
004012BC mov ecx,10h
004012C1 mov eax,0CCCCCCCCh
004012C6 rep stos dword ptr [edi]
13: Accumulation(12);
004012C8 push 0Ch
004012CA call @ILT+10(Accumulation) (0040100f)
004012CF add esp,4
14:
15: return 0;
004012D2 xor eax,eax
16: }
004012D4 pop edi
004012D5 pop esi
004012D6 pop ebx
004012D7 add esp,40h
004012DA cmp ebp,esp
004012DC call __chkesp (004081b0)
004012E1 mov esp,ebp
004012E3 pop ebp
004012E4 ret
主に短絡状況を見る:第1段のコード分析から&&前の式が再帰的に飛び出す条件であることがわかる
これはまさに短絡現象を利用してコードを精錬させたものである.
演算子のショートカットを見てみましょう.
ソースコードは基本的に同じで、アセンブリ対照コードを直接見ます.
1: #include
2:
3: using namespace std;
4:
5: int Accumulation(int nNumber)
6: {
00401250 push ebp
00401251 mov ebp,esp
00401253 sub esp,40h
00401256 push ebx
00401257 push esi
00401258 push edi
00401259 lea edi,[ebp-40h]
0040125C mov ecx,10h
00401261 mov eax,0CCCCCCCCh
00401266 rep stos dword ptr [edi]
7: (nNumber==0) || (nNumber += Accumulation(nNumber - 1));
00401268 cmp dword ptr [ebp+8],0
0040126C je Accumulation+35h (00401285)
0040126E mov eax,dword ptr [ebp+8]
00401271 sub eax,1
00401274 push eax
00401275 call @ILT+10(Accumulation) (0040100f)
0040127A add esp,4
0040127D mov ecx,dword ptr [ebp+8]
00401280 add ecx,eax
00401282 mov dword ptr [ebp+8],ecx
8: return nNumber;
00401285 mov eax,dword ptr [ebp+8]
9: }
00401288 pop edi
00401289 pop esi
0040128A pop ebx
0040128B add esp,40h
0040128E cmp ebp,esp
00401290 call __chkesp (004081b0)
00401295 mov esp,ebp
00401297 pop ebp
00401298 ret
10:
11: int main(int argc,char *argv[])
12: {
004012B0 push ebp
004012B1 mov ebp,esp
004012B3 sub esp,40h
004012B6 push ebx
004012B7 push esi
004012B8 push edi
004012B9 lea edi,[ebp-40h]
004012BC mov ecx,10h
004012C1 mov eax,0CCCCCCCCh
004012C6 rep stos dword ptr [edi]
13: Accumulation(12);
004012C8 push 0Ch
004012CA call @ILT+10(Accumulation) (0040100f)
004012CF add esp,4
14:
15: return 0;
004012D2 xor eax,eax
16: }
004012D4 pop edi
004012D5 pop esi
004012D6 pop ebx
004012D7 add esp,40h
004012DA cmp ebp,esp
004012DC call __chkesp (004081b0)
004012E1 mov esp,ebp
004012E3 pop ebp
004012E4 ret
比較すると,異なる論理演算子を用いたが,同じアセンブリコードを生成し,それらの機能が完全に同じであることを示した.
条件式:
三目表現はよく知られていませんが、Cの中には一つしかありません.
式1?式2:式3
条件式には4つの変換スキームがあります.
>シナリオ1:式1は単純な比較であり、式2と式3の差は1に等しい
>シナリオ2:式1は単純な比較であり、式2と式3の差は1より大きい
>シナリオ3:式1は複雑な比較であり、式2と式3の差は1より大きい
>シナリオ4:式2と式3は変数であり、最適化されていない
例:
シナリオ1:
ソース:
int Condition(int argc,int n)
{
return argc == 5 ? 5 : 6;
}
アセンブリコード:
00401678 xor eax,eax
0040167A cmp dword ptr [ebp+8],5
0040167E setne al // 0, al, 0
00401681 add eax,5
しかしsetneも1の差のバランスにしか使えず、1より大きいと無力になります
シナリオ2:
ソース:
int Condition(int argc,int n)
{
return argc == 5 ? 4 : 10;
}
アセンブリコード:
00401678 mov eax,dword ptr [ebp+8]
0040167B sub eax,5
0040167E neg eax
00401680 sbb eax,eax
00401682 and eax,6
00401685 add eax,4
arg=5という等値比較では,VC++は減算と補正演算を用いて真値か否かを判断する.
argcが5に等しくない限り、sub命令を実行した後、eaxの値は0ではない.
そしてneg命令によりeaxの符号ビットが変化し,CFが1となる.
次に、ビット減算eax=eax−eax−CFを実行する.CFが1の場合、eaxの値は0 xFFFFFFFFFF、そうでない場合は0
eaxと6を使用してビットと演算を行った後、eaxの数値が元の値-1であれば、解果は6、プラス4は数値10に等しい.
そうでない場合argc==5、eaxは常に0で、4を加えて数値4になります.
ただし、式1が1区間である場合は、これではだめです
シナリオ3:
ソース:
int Condition(int argc,int n)
{
return argc <= 8 ? 4 : 10;
}
アセンブリコード分析:
00401678 xor eax,eax // eax
0040167A cmp dword ptr [ebp+8],8 // argc 8
0040167E setg al // argc>8 al=1, 0
00401681 dec eax //eax 1,eax =0xFFFFFFFF 0
00401682 and al,0FAh //eax 0xFFFFFFFA(-6 ) 0
00401685 add eax,0Ah //eax 4 10
上はもうはっきり説明した.
ただし、式2または式3の値が未知数の場合、従来の方法では最適化できません.
コンパイラは通常の文の流れに基づいて比較し、判断します.
シナリオ4:
ソース:
int Condition(int argc,int n)
{
return argc ? 4 : n;
}
アセンブリ言語解析:
00401448 cmp dword ptr [ebp+8],0
0040144C je Condition+27h (00401457)
0040144E mov dword ptr [ebp-4],8
00401455 jmp Condition+2Dh (0040145d)
00401457 mov eax,dword ptr [ebp+0Ch]
0040145A mov dword ptr [ebp-4],eax
0040145D mov eax,dword ptr [ebp-4]
00401466 ret
ビット演算
<<左シフト演算,最高位左シフトCF,最低位補零
>>右シフト演算、最高位不変、最低位右シフトCF中
コードがどのように実装されているかを見てみましょう.
ソース:
#include
using namespace std;
int BitOperation(int argc)
{
argc = argc << 3;
argc = argc >> 5;
argc = argc | 0xFFFF0000;
argc = argc & 0x0000FFFF;
argc = argc ^ 0xFFFF0000;
argc = ~argc;
return argc;
}
int main()
{
int a = 5;
BitOperation(a);
return 0;
}
アセンブリコード分析:
1: #include
2:
3: using namespace std;
4:
5: int BitOperation(int argc)
6: {
00401250 push ebp
00401251 mov ebp,esp
00401253 sub esp,40h
00401256 push ebx
00401257 push esi
00401258 push edi
00401259 lea edi,[ebp-40h]
0040125C mov ecx,10h
00401261 mov eax,0CCCCCCCCh
00401266 rep stos dword ptr [edi]
7: argc = argc << 3;
00401268 mov eax,dword ptr [ebp+8]
0040126B shl eax,3
0040126E mov dword ptr [ebp+8],eax
8: argc = argc >> 5;
00401271 mov ecx,dword ptr [ebp+8]
00401274 sar ecx,5
00401277 mov dword ptr [ebp+8],ecx
9: argc = argc | 0xFFFF0000;
0040127A mov edx,dword ptr [ebp+8]
0040127D or edx,0FFFF0000h
00401283 mov dword ptr [ebp+8],edx
10: argc = argc & 0x0000FFFF;
00401286 mov eax,dword ptr [ebp+8]
00401289 and eax,0FFFFh
0040128E mov dword ptr [ebp+8],eax
11: argc = argc ^ 0xFFFF0000;
00401291 mov ecx,dword ptr [ebp+8]
00401294 xor ecx,0FFFF0000h
0040129A mov dword ptr [ebp+8],ecx
12: argc = ~argc;
0040129D mov edx,dword ptr [ebp+8]
004012A0 not edx
004012A2 mov dword ptr [ebp+8],edx
13:
14: return argc;
004012A5 mov eax,dword ptr [ebp+8]
15: }
004012A8 pop edi
004012A9 pop esi
004012AA pop ebx
004012AB mov esp,ebp
004012AD pop ebp
004012AE ret
16:
17: int main()
18: {
004012D0 push ebp
004012D1 mov ebp,esp
004012D3 sub esp,44h
004012D6 push ebx
004012D7 push esi
004012D8 push edi
004012D9 lea edi,[ebp-44h]
004012DC mov ecx,11h
004012E1 mov eax,0CCCCCCCCh
004012E6 rep stos dword ptr [edi]
19: int a = 5;
004012E8 mov dword ptr [ebp-4],5
20: BitOperation(a);
004012EF mov eax,dword ptr [ebp-4]
004012F2 push eax
004012F3 call @ILT+10(BitOperation) (0040100f)
004012F8 add esp,4
21:
22: return 0;
004012FB xor eax,eax
23: }
004012FD pop edi
004012FE pop esi
004012FF pop ebx
00401300 add esp,44h
00401303 cmp ebp,esp
00401305 call __chkesp (004081d0)
0040130A mov esp,ebp
0040130C pop ebp
0040130D ret
上の逆アセンブリコードは比較的簡単で、記号のあるビット演算です.
次の記号のないものは少し違います.
ソース:
#include
using namespace std;
void BitOperation(int argc)
{
unsigned int nVar = argc;
nVar <<= 3;
nVar >>= 5;
}
int main()
{
int a = 5;
BitOperation(a);
return 0;
}
アセンブリコード分析:
1: #include
2:
3: using namespace std;
4:
5: void BitOperation(int argc)
6: {
00401250 push ebp
00401251 mov ebp,esp
00401253 sub esp,44h
00401256 push ebx
00401257 push esi
00401258 push edi
00401259 lea edi,[ebp-44h]
0040125C mov ecx,11h
00401261 mov eax,0CCCCCCCCh
00401266 rep stos dword ptr [edi]
7: unsigned int nVar = argc;
00401268 mov eax,dword ptr [ebp+8]
0040126B mov dword ptr [ebp-4],eax
8: nVar <<= 3;
0040126E mov ecx,dword ptr [ebp-4]
00401271 shl ecx,3
00401274 mov dword ptr [ebp-4],ecx
9: nVar >>= 5;
00401277 mov edx,dword ptr [ebp-4]
0040127A shr edx,5
0040127D mov dword ptr [ebp-4],edx
10:
11: }
00401280 pop edi
00401281 pop esi
00401282 pop ebx
00401283 mov esp,ebp
00401285 pop ebp
00401286 ret
12:
13: int main()
14: {
004012A0 push ebp
004012A1 mov ebp,esp
004012A3 sub esp,44h
004012A6 push ebx
004012A7 push esi
004012A8 push edi
004012A9 lea edi,[ebp-44h]
004012AC mov ecx,11h
004012B1 mov eax,0CCCCCCCCh
004012B6 rep stos dword ptr [edi]
15: int a = 5;
004012B8 mov dword ptr [ebp-4],5
16: BitOperation(a);
004012BF mov eax,dword ptr [ebp-4]
004012C2 push eax
004012C3 call @ILT+10(BitOperation) (0040100f)
004012C8 add esp,4
17:
18: return 0;
004012CB xor eax,eax
19: }
004012CD pop edi
004012CE pop esi
004012CF pop ebx
004012D0 add esp,44h
004012D3 cmp ebp,esp
004012D5 call __chkesp (004081a0)
004012DA mov esp,ebp
004012DC pop ebp
004012DD ret