x86におけるCF(キャリーフラグ)の動作について

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
ユーマ
記事: 28
登録日時: 1年前

x86におけるCF(キャリーフラグ)の動作について

#1

投稿記事 by ユーマ » 2ヶ月前

こんにちは、ユーマと申します。
今回は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

かずま

Re: x86におけるCF(キャリーフラグ)の動作について

#2

投稿記事 by かずま » 2ヶ月前

cmp a, b を実行したときの CF(キャリーフラグ) は、
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
setb は、setc や setnae というニーモニックと同じ
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
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ビットの符号付き整数の
範囲外となる場合です。

ユーマ
記事: 28
登録日時: 1年前

Re: x86におけるCF(キャリーフラグ)の動作について

#3

投稿記事 by ユーマ » 2ヶ月前

かずまさん、こんにちは!
わざわざプログラムまで乗せてくださってありがとうございますm(_ _)m

2の補数表現で考えていたのがそもそもの間違いだったようです。
符号あり・なしの両方について理解が深まりました。
OFの解説と交えて記述していただいたので非常にわかりやすかったです。
自分も同じプログラムを作って確かめてみたいと思います。
どうもありがとうございました!

かずま

Re: x86におけるCF(キャリーフラグ)の動作について

#4

投稿記事 by かずま » 2ヶ月前

すみません。#2 の回答で示した gcc のコンパイル結果は最適化して
いなかったので、次のように無駄なコードが出ていました。

コード:

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   
gcc は関数を呼び出すとき、最初の 4つの引数は
%ecx, %edx, %r8d, %r9d の 4つのレジスタで渡しています。
最適化しないコードでは、レジスタで渡された引数を
スタックに積み直しているようです。

ユーマ
記事: 28
登録日時: 1年前

Re: x86におけるCF(キャリーフラグ)の動作について

#5

投稿記事 by ユーマ » 2ヶ月前

カズマさん
なるほど、スタックを使用すると遅いのでレジスタで渡しているわけですね
わざわざ追記までしていただいてありがとうございます!

かずま

Re: x86におけるCF(キャリーフラグ)の動作について

#6

投稿記事 by かずま » 2ヶ月前

かずま さんが書きました:
2ヶ月前
gcc は関数を呼び出すとき、最初の 4つの引数は
%ecx, %edx, %r8d, %r9d の 4つのレジスタで渡しています。
最適化しないコードでは、レジスタで渡された引数を
スタックに積み直しているようです。
これは、Windows の cygwin の gcc の場合でした。
Linux(Ubuntu) の gcc では、6つの引数を次のレジスタで渡していました。
%edi, %esi, %edx, %ecx, %r8d, %r9d

ユーマ
記事: 28
登録日時: 1年前

Re: x86におけるCF(キャリーフラグ)の動作について

#7

投稿記事 by ユーマ » 2ヶ月前

gccではedi・esiも引数に使えるんですね...
細かい部分までどうもありがとうございます!
こんなに丁寧に教えてもらえるとは...この掲示板(?)はすごいですね。

返信

“C言語何でも質問掲示板” へ戻る