こんにちは、ユーマと申します。
今回はx86におけるCF(キャリーフラグ)について疑問を持ったので質問させていただきました。
自分が勉強したサイトでは「cmp a, bを行った場合、a >= bのときはCF=0、a < bのときはCF=1になる」と記述されていました。
そして、実際にデバッガを用いて「5 - 3」「5 - 6」で実験した所、前者ではCF=0、後者ではCF=1となっていました。
ですが、自分の考えでは「cmp a, bを行った場合、a >= bのときはCF=1、a < bのときはCF=0になる」ような気がするのです。
例えば、x86では2の補数表現で計算しているらしいので、cmp命令を実行した際に「5 - 3 = 0x00000005 + 0xfffffffd = 0x100000002」となり最上位ビットから繰り上がりが起こります。
つまり、CF=1となると考えられます。
同様に「5 - 6 = 0x00000005 + 0xfffffffa = 0xffffffff」となり、最上位ビットからの繰り上がりは起こりません。
つまり、CF=0になると考えられます。
実際の動作と自分の考えとが全くの反対で困惑しております。
おそらく決定的な勘違いや理解不足だと思うのですが、いくら考えても解決の糸口がつかめません。
CFが最上位ビットからの繰り上がりと思い込んでいることが問題なのでしょうか...
どなたかお力を貸してください。
自分の考えと同じサイトを発見したのでここに貼らせていただきます。
http://donkeyhacks.zouri.jp/databank/65 ... carry.html
x86におけるCF(キャリーフラグ)の動作について
Re: x86におけるCF(キャリーフラグ)の動作について
cmp a, b を実行したときの CF(キャリーフラグ) は、
a と b を符号無し整数として、比較した結果になります。
ハードウェアの比較回路がどう実現されているかには関係ありません。
2の補数表現で引き算をしていると考えないでください。
符号無し整数の比較で、a < b であれば CF は 1 になります。
int u_cmp(unsigned a, unsigned b) { return a < b; }
このコードを gcc でコンパイルすると
setb は、setc や setnae というニーモニックと同じ
0f 92 というコードになります。
これは、CF の値を AL にセットする命令です。
では、2の補数表現による比較はどうなるでしょうか?
int s_cmp(int a, int b) { return a < b; }
このコードを gcc でコンパイルすると
setl は、setnge というニーモニックと同じで
0f 9c というコードになります。
これは SF(符号フラグ) と OF(オーバーフローフラグ) が
異なる場合に AL に 1 をセットする命令です。
cmp 5, 3 では、引き算結果は 2 (0x00000002) で、符号は正なので SF = 0。
引き算でオーバーフローが起こらないので OF = 0 です。
SF と OF が等しいので AL には 0 がセットされます。
a < b の演算結果として偽を返します。
cmp 5, 6 では、引き算結果は -1 (0xffffffff) で、符号は負なので SF = 1。
引き算でオーバーフローが起こらないので OF = 0 です。
SF と OF が異なるので AL には 1 がセットされます。
a < b の演算結果として真を返します。
OF = 1 になるのはどんな時かというと、例えば、
cmp 20億, -20億 という符号付整数を比較した場合、
引き算の結果が 40億となり、32ビットの符号付き整数の
範囲外となる場合です。
a と b を符号無し整数として、比較した結果になります。
ハードウェアの比較回路がどう実現されているかには関係ありません。
2の補数表現で引き算をしていると考えないでください。
符号無し整数の比較で、a < b であれば CF は 1 になります。
int u_cmp(unsigned a, unsigned b) { return a < b; }
このコードを gcc でコンパイルすると
u_cmp:
pushq %rbp
movq %rsp, %rbp
movl %ecx, 16(%rbp)
movl %edx, 24(%rbp)
movl 16(%rbp), %eax
cmpl 24(%rbp), %eax
setb %al
movzbl %al, %eax
popq %rbp
ret
0f 92 というコードになります。
これは、CF の値を AL にセットする命令です。
では、2の補数表現による比較はどうなるでしょうか?
int s_cmp(int a, int b) { return a < b; }
このコードを gcc でコンパイルすると
s_cmp:
pushq %rbp
movq %rsp, %rbp
movl %ecx, 16(%rbp)
movl %edx, 24(%rbp)
movl 16(%rbp), %eax
cmpl 24(%rbp), %eax
setl %al
movzbl %al, %eax
popq %rbp
ret
0f 9c というコードになります。
これは SF(符号フラグ) と OF(オーバーフローフラグ) が
異なる場合に AL に 1 をセットする命令です。
cmp 5, 3 では、引き算結果は 2 (0x00000002) で、符号は正なので SF = 0。
引き算でオーバーフローが起こらないので OF = 0 です。
SF と OF が等しいので AL には 0 がセットされます。
a < b の演算結果として偽を返します。
cmp 5, 6 では、引き算結果は -1 (0xffffffff) で、符号は負なので SF = 1。
引き算でオーバーフローが起こらないので OF = 0 です。
SF と OF が異なるので AL には 1 がセットされます。
a < b の演算結果として真を返します。
OF = 1 になるのはどんな時かというと、例えば、
cmp 20億, -20億 という符号付整数を比較した場合、
引き算の結果が 40億となり、32ビットの符号付き整数の
範囲外となる場合です。
Re: x86におけるCF(キャリーフラグ)の動作について
かずまさん、こんにちは!
わざわざプログラムまで乗せてくださってありがとうございますm(_ _)m
2の補数表現で考えていたのがそもそもの間違いだったようです。
符号あり・なしの両方について理解が深まりました。
OFの解説と交えて記述していただいたので非常にわかりやすかったです。
自分も同じプログラムを作って確かめてみたいと思います。
どうもありがとうございました!
わざわざプログラムまで乗せてくださってありがとうございますm(_ _)m
2の補数表現で考えていたのがそもそもの間違いだったようです。
符号あり・なしの両方について理解が深まりました。
OFの解説と交えて記述していただいたので非常にわかりやすかったです。
自分も同じプログラムを作って確かめてみたいと思います。
どうもありがとうございました!
Re: x86におけるCF(キャリーフラグ)の動作について
すみません。#2 の回答で示した gcc のコンパイル結果は最適化して
いなかったので、次のように無駄なコードが出ていました。
最適化すると次のようになります。
gcc は関数を呼び出すとき、最初の 4つの引数は
%ecx, %edx, %r8d, %r9d の 4つのレジスタで渡しています。
最適化しないコードでは、レジスタで渡された引数を
スタックに積み直しているようです。
いなかったので、次のように無駄なコードが出ていました。
0000000000000000 <u_cmp>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 89 4d 10 mov %ecx,0x10(%rbp)
7: 89 55 18 mov %edx,0x18(%rbp)
a: 8b 45 10 mov 0x10(%rbp),%eax
d: 3b 45 18 cmp 0x18(%rbp),%eax
10: 0f 92 c0 setb %al
13: 0f b6 c0 movzbl %al,%eax
16: 5d pop %rbp
17: c3 retq
0000000000000018 <s_cmp>:
18: 55 push %rbp
19: 48 89 e5 mov %rsp,%rbp
1c: 89 4d 10 mov %ecx,0x10(%rbp)
1f: 89 55 18 mov %edx,0x18(%rbp)
22: 8b 45 10 mov 0x10(%rbp),%eax
25: 3b 45 18 cmp 0x18(%rbp),%eax
28: 0f 9c c0 setl %al
2b: 0f b6 c0 movzbl %al,%eax
2e: 5d pop %rbp
2f: c3 retq
0000000000000000 <u_cmp>:
0: 39 d1 cmp %edx,%ecx
2: 0f 92 c0 setb %al
5: 0f b6 c0 movzbl %al,%eax
8: c3 retq
0000000000000009 <s_cmp>:
9: 39 d1 cmp %edx,%ecx
b: 0f 9c c0 setl %al
e: 0f b6 c0 movzbl %al,%eax
11: c3 retq
%ecx, %edx, %r8d, %r9d の 4つのレジスタで渡しています。
最適化しないコードでは、レジスタで渡された引数を
スタックに積み直しているようです。
Re: x86におけるCF(キャリーフラグ)の動作について
カズマさん
なるほど、スタックを使用すると遅いのでレジスタで渡しているわけですね
わざわざ追記までしていただいてありがとうございます!
なるほど、スタックを使用すると遅いのでレジスタで渡しているわけですね
わざわざ追記までしていただいてありがとうございます!
Re: x86におけるCF(キャリーフラグ)の動作について
これは、Windows の cygwin の gcc の場合でした。かずま さんが書きました: ↑4年前gcc は関数を呼び出すとき、最初の 4つの引数は
%ecx, %edx, %r8d, %r9d の 4つのレジスタで渡しています。
最適化しないコードでは、レジスタで渡された引数を
スタックに積み直しているようです。
Linux(Ubuntu) の gcc では、6つの引数を次のレジスタで渡していました。
%edi, %esi, %edx, %ecx, %r8d, %r9d
Re: x86におけるCF(キャリーフラグ)の動作について
gccではedi・esiも引数に使えるんですね...
細かい部分までどうもありがとうございます!
こんなに丁寧に教えてもらえるとは...この掲示板(?)はすごいですね。
細かい部分までどうもありがとうございます!
こんなに丁寧に教えてもらえるとは...この掲示板(?)はすごいですね。