.file "neg.s" .text .globl main main: movb $-128, %al !! /t $eax = 10000000, /d $eax = 128 movw $-128, %bx !! /t $ebx = 1111111110000000, /d $ebx = 65408 movl $-128, %ecx !! /t $ecx = 11111111111111111111111110000000, /d $ecx = -128 movb $0b10000001, %al ! -127 in 8bits. it's -01111111. movl $0xffffffff, %ebx movb $0b10000001, %bl ! -127 in 8bits. it's -01111111. nop 上の実験から,符号は32ビット目で判断されることが分かる.たとえ,8ビット長で一番上のビットを立てても負数にはならないことが, movb $0b10000001, %al ! -127 in 8bits. it's -01111111. の行から分かる.この結果は,$eax = 129 になる. 最後の2行のようにすれば,$ebx = -127として認識される. それでは,C言語においてcharやintなどの32ビット未満のデータ型で負数を代入するとき,内部ではどのように表現されているのだろうか.以下のようなプログラムで確認してみる. void main(void){ char first = -127; short int second = -127; long int third = -127; char fourth = -128; char fifth = 128; static char varbyte; static short int varword; static long int varlong; varbyte = first; varword = second; varlong = third; varbyte = fourth; varbyte = fifth; } $ gcc -S neg.c $ cat neg.s .file "neg.c" .version "01.01" gcc2_compiled.: .local varbyte.2 .comm varbyte.2,1,1 .local varword.3 .comm varword.3,2,2 .local varlong.4 .comm varlong.4,4,4 .text .align 16 .globl main .type main,@function main: pushl %ebp movl %esp,%ebp subl $12,%esp movb $-127,-1(%ebp) movw $-127,-4(%ebp) movl $-127,-8(%ebp) movb $-128,-9(%ebp) movb $-128,-10(%ebp) movb -1(%ebp),%al movb %al,varbyte.2 movw -4(%ebp),%ax movw %ax,varword.3 movl -8(%ebp),%eax movl %eax,varlong.4 movb -9(%ebp),%al movb %al,varbyte.2 movb -10(%ebp),%al movb %al,varbyte.2 .L1: movl %ebp,%esp popl %ebp ret .Lfe1: .size main,.Lfe1-main .ident "GCC: (GNU) 2.7.2.1" $ gcc -o neg neg.s $ gdb -x neg.gdb neg GDB is free software and you are welcome to distribute copies of it under certain conditions; type "show copying" to see the conditions. There is absolutely no warranty for GDB; type "show warranty" for details. GDB 4.16 (i586-debian-linux), Copyright 1996 Free Software Foundation, Inc... Breakpoint 1 at 0x8048146 (gdb) run Starting program: /home/wshito/program/as/intro/neg Breakpoint 1, 0x8048146 in main () 3: /t $eax = 0 2: /d $eax = 0 1: x/i $eip 0x8048146 : movb $0x81,0xffffffff(%ebp) (gdb) si !!(char first = -127) 0x804814a in main () 3: /t $eax = 0 2: /d $eax = 0 1: x/i $eip 0x804814a : movw $0xff81,0xfffffffc(%ebp) (gdb) si !!(short int second = -127) 0x8048150 in main () 3: /t $eax = 0 2: /d $eax = 0 1: x/i $eip 0x8048150 : movl $0xffffff81,0xfffffff8(%ebp) (gdb) si !!(long int third = -127) 0x8048157 in main () 3: /t $eax = 0 2: /d $eax = 0 1: x/i $eip 0x8048157 : movb $0x80,0xfffffff7(%ebp) (gdb) si !!(char fourth = -128) 0x804815b in main () 3: /t $eax = 0 2: /d $eax = 0 1: x/i $eip 0x804815b : movb $0x80,0xfffffff6(%ebp) (gdb) si !!(char fifth = 128) 0x804815f in main () 3: /t $eax = 0 2: /d $eax = 0 1: x/i $eip 0x804815f : movb 0xffffffff(%ebp),%al (gdb) si 0x8048162 in main () 3: /t $eax = 10000001 2: /d $eax = 129 1: x/i $eip 0x8048162 : movb %al,0x805e0b4 !!varbyte (gdb) si !!(varbyte = first (-127)) 0x8048168 in main () 3: /t $eax = 10000001 2: /d $eax = 129 1: x/i $eip 0x8048168 : movw 0xfffffffc(%ebp),%ax (gdb) si 0x804816c in main () 3: /t $eax = 1111111110000001 2: /d $eax = 65409 1: x/i $eip 0x804816c : movw %ax,0x805e0b6 !!varword (gdb) si !!(varword = second (-127)) 0x8048172 in main () 3: /t $eax = 1111111110000001 2: /d $eax = 65409 1: x/i $eip 0x8048172 : movl 0xfffffff8(%ebp),%eax (gdb) si 0x8048175 in main () 3: /t $eax = 11111111111111111111111110000001 2: /d $eax = -127 1: x/i $eip 0x8048175 : movl %eax,0x805e0b8 !!varlong (gdb) si !!(varlong = third (-127)) 0x804817a in main () 3: /t $eax = 11111111111111111111111110000001 2: /d $eax = -127 1: x/i $eip 0x804817a : movb 0xfffffff7(%ebp),%al (gdb) si 0x804817d in main () 3: /t $eax = 11111111111111111111111110000000 2: /d $eax = -128 1: x/i $eip 0x804817d : movb %al,0x805e0b4 !!varbyte (gdb) si !!(varbyte = fourth (-128)) 0x8048183 in main () 3: /t $eax = 11111111111111111111111110000000 2: /d $eax = -128 1: x/i $eip 0x8048183 : movb 0xfffffff6(%ebp),%al (gdb) si 0x8048186 in main () 3: /t $eax = 11111111111111111111111110000000 2: /d $eax = -128 1: x/i $eip 0x8048186 : movb %al,0x805e0b4 !!varbyte (gdb) si !!(varbyte = fourth (128)) 0x804818c in main () 3: /t $eax = 11111111111111111111111110000000 2: /d $eax = -128 1: x/i $eip 0x804818c : movl %ebp,%esp (gdb) si 0x804818e in main () 3: /t $eax = 11111111111111111111111110000000 2: /d $eax = -128 1: x/i $eip 0x804818e : popl %ebp (gdb) si 0x804818f in main () 3: /t $eax = 11111111111111111111111110000000 2: /d $eax = -128 1: x/i $eip 0x804818f : ret (gdb) si 0x80480ee in ___crt_dummy__ () 3: /t $eax = 11111111111111111111111110000000 2: /d $eax = -128 1: x/i $eip 0x80480ee <___crt_dummy__+94>: pushl %eax もし,neg.cの一部の順番を換えるとどうなるか. $ cat neg.c void main(void){ char first = -127; short int second = -127; long int third = -127; char fourth = -128; char fifth = 128; static char varbyte; static short int varword; static long int varlong; varbyte = fourth; varbyte = fifth; varbyte = first; varword = second; varlong = third; } $ gcc -S neg.c $ cat neg.s .file "neg.c" .version "01.01" gcc2_compiled.: .local varbyte.2 .comm varbyte.2,1,1 .local varword.3 .comm varword.3,2,2 .local varlong.4 .comm varlong.4,4,4 .text .align 16 .globl main .type main,@function main: pushl %ebp movl %esp,%ebp subl $12,%esp movb $-127,-1(%ebp) movw $-127,-4(%ebp) movl $-127,-8(%ebp) movb $-128,-9(%ebp) movb $-128,-10(%ebp) movb -9(%ebp),%al movb %al,varbyte.2 movb -10(%ebp),%al movb %al,varbyte.2 movb -1(%ebp),%al movb %al,varbyte.2 movw -4(%ebp),%ax movw %ax,varword.3 movl -8(%ebp),%eax movl %eax,varlong.4 .L1: movl %ebp,%esp popl %ebp ret .Lfe1: .size main,.Lfe1-main .ident "GCC: (GNU) 2.7.2.1" $ gdb -x neg.gdb neg GDB is free software and you are welcome to distribute copies of it under certain conditions; type "show copying" to see the conditions. There is absolutely no warranty for GDB; type "show warranty" for details. GDB 4.16 (i586-debian-linux), Copyright 1996 Free Software Foundation, Inc... Breakpoint 1 at 0x8048146 (gdb) run Starting program: /home/wshito/program/as/intro/neg Breakpoint 1, 0x8048146 in main () 3: /t $eax = 0 2: /d $eax = 0 1: x/i $eip 0x8048146 : movb $0x81,0xffffffff(%ebp) (gdb) si 0x804814a in main () 3: /t $eax = 0 2: /d $eax = 0 1: x/i $eip 0x804814a : movw $0xff81,0xfffffffc(%ebp) (gdb) si 0x8048150 in main () 3: /t $eax = 0 2: /d $eax = 0 1: x/i $eip 0x8048150 : movl $0xffffff81,0xfffffff8(%ebp) (gdb) si 0x8048157 in main () 3: /t $eax = 0 2: /d $eax = 0 1: x/i $eip 0x8048157 : movb $0x80,0xfffffff7(%ebp) (gdb) si 0x804815b in main () 3: /t $eax = 0 2: /d $eax = 0 1: x/i $eip 0x804815b : movb $0x80,0xfffffff6(%ebp) (gdb) si 0x804815f in main () 3: /t $eax = 0 2: /d $eax = 0 1: x/i $eip 0x804815f : movb 0xfffffff7(%ebp),%al (gdb) si 0x8048162 in main () 3: /t $eax = 10000000 2: /d $eax = 128 1: x/i $eip 0x8048162 : movb %al,0x805e0b4 (gdb) si 0x8048168 in main () 3: /t $eax = 10000000 2: /d $eax = 128 1: x/i $eip 0x8048168 : movb 0xfffffff6(%ebp),%al (gdb) si 0x804816b in main () 3: /t $eax = 10000000 2: /d $eax = 128 1: x/i $eip 0x804816b : movb %al,0x805e0b4 (gdb) si 0x8048171 in main () 3: /t $eax = 10000000 2: /d $eax = 128 1: x/i $eip 0x8048171 : movb 0xffffffff(%ebp),%al (gdb) si 0x8048174 in main () 3: /t $eax = 10000001 2: /d $eax = 129 1: x/i $eip 0x8048174 : movb %al,0x805e0b4 (gdb) si 0x804817a in main () 3: /t $eax = 10000001 2: /d $eax = 129 1: x/i $eip 0x804817a : movw 0xfffffffc(%ebp),%ax (gdb) si 0x804817e in main () 3: /t $eax = 1111111110000001 2: /d $eax = 65409 1: x/i $eip 0x804817e : movw %ax,0x805e0b6 (gdb) si 0x8048184 in main () 3: /t $eax = 1111111110000001 2: /d $eax = 65409 1: x/i $eip 0x8048184 : movl 0xfffffff8(%ebp),%eax (gdb) si 0x8048187 in main () 3: /t $eax = 11111111111111111111111110000001 2: /d $eax = -127 1: x/i $eip 0x8048187 : movl %eax,0x805e0b8 (gdb) si 0x804818c in main () 3: /t $eax = 11111111111111111111111110000001 2: /d $eax = -127 1: x/i $eip 0x804818c : movl %ebp,%esp (gdb) si 0x804818e in main () 3: /t $eax = 11111111111111111111111110000001 2: /d $eax = -127 1: x/i $eip 0x804818e : popl %ebp (gdb) si 0x804818f in main () 3: /t $eax = 11111111111111111111111110000001 2: /d $eax = -127 1: x/i $eip 0x804818f : ret (gdb) si 0x80480ee in ___crt_dummy__ () 3: /t $eax = 11111111111111111111111110000001 2: /d $eax = -127 1: x/i $eip 0x80480ee <___crt_dummy__+94>: pushl %eax (gdb) si 0x80480ef in ___crt_dummy__ () 3: /t $eax = 11111111111111111111111110000001 2: /d $eax = -127 1: x/i $eip 0x80480ef <___crt_dummy__+95>: call 0x8048244 (gdb) quit ちなみに,上のプログラムで,var*変数をC言語からプリントしてやると,以下の結果を得る. varbyte(-128) = -128 varbyte(128) = -128 <-- Cでは,128を代入しているが,アセンブラでは-128になっている. varbyte(-127) = -127 varword(-127) = -127 varlong(-127) = -127 変数名の横の括弧の中の値は,それぞれ代入した値である. アセンブラは,なぜ128を-128に間違えるのか?charでは,本来+-127までしか表せない.したがって,unsigned charにしない限りアセンブラは最高位ビットを符号ビットとして扱う.128は,二進数で10000000である.8ビット長での2の補数表現の基準の数は,2^8(2の8乗)だから二進数で100000000である.したがって,128の負数は二の補数表現で100000000-10000000=10000000.したがって,コンピュータに128と-128の区別がつかなくなるのである.もし,最高位ビットを符号ビットとすれば,これはまさしく負の値.二の補数表現ではビットが立っているもののうち一番最下位のアドレスビットを残し,それより高いアドレスのビットを反転させるので,結局最高位ビットが残り,その上のビットを反転させようとする.しかし,その上のビットは1バイト長ではないので,結局得られた128にマイナスをつけて-128となる. 分かったこと.gdbデバッガは,符号ビットを常に32ビット長の最高位ビットとして扱う.コンピュータは,データ型のビット長の最高位ビットを符号ビットとして扱う. 実験.今までの議論から,-129を代入すれば,129の二進数10000001の補数を取るから,コンピュータは01111111を代入するはずである.これはすなわち,127である.反対に,129を表示させようとすれば10000001を代入するから-127が表示されるだろう. void main(void){ char one = -129; char two = 129 static char three; three = one; printf("three(-129) = %d\n", three); three = two; printf("three(129) = %d\n", three); } $ gcc -o neg2 neg2.c neg2.c: In function `main': neg2.c:2: warning: overflow in implicit constant conversion $ neg2 three(-129) = 127 three(129) = -127 思った通りである.しかし,コンパイラは利口である.オーバーフローを検知している.しかし,ここでまた疑問が生じる.-128の時には,コンパイラはオーバーフローを検知しなかったのに,なぜ-129の時には検知したのだろうか.アセンブラを前のものと比較すると,前のは-128だが,今回のはアセンブラの段階で既に127にされていることが分かる. たとえ下のアセンブラの127を-129に,-127を129に書き換えて,アセンブラをコンパイルしても結果は上と同じになる.しかし,この場合にはオーバーフローの警告はでない.と,言うことは,コンパイラがアセンブラを吐き出す手前で一度数値を評価していることになる.そして,オーバーフローは-128では引っ掛からずに-129で引っ掛かるのも,コンパイラの内部であることが分かる. $ gcc -S neg2.c neg2.c: In function `main': neg2.c:2: warning: overflow in implicit constant conversion $ cat neg2.s .file "neg2.c" .version "01.01" gcc2_compiled.: .local three.2 .comm three.2,1,1 .section .rodata .LC0: .string "three(-129) = %d\n" .LC1: .string "three(129) = %d\n" .text .align 16 .globl main .type main,@function main: pushl %ebp movl %esp,%ebp subl $4,%esp movb $127,-1(%ebp) movb $-127,-2(%ebp) movb -1(%ebp),%al movb %al,three.2 movsbl three.2,%eax <--- movsblで,符号が保存されながらlongに変換. pushl %eax $eax = 10000001 から pushl $.LC0 $eax = 11111111111111111111111110000001 になる. call printf addl $8,%esp movb -2(%ebp),%al movb %al,three.2 movsbl three.2,%eax pushl %eax pushl $.LC1 call printf addl $8,%esp .L1: movl %ebp,%esp popl %ebp ret .Lfe1: .size main,.Lfe1-main .ident "GCC: (GNU) 2.7.2.1" $ gdb neg2 省略 (gdb) run Starting program: /home/wshito/program/as/intro/neg2 Breakpoint 1, 0x8048146 in main () 3: /t $eax = 0 2: /d $eax = 0 1: x/i $eip 0x8048146 : movb $0x7f,0xffffffff(%ebp) (gdb) nexti 0x804814a in main () 3: /t $eax = 0 2: /d $eax = 0 1: x/i $eip 0x804814a : movb $0x81,0xfffffffe(%ebp) (gdb) nexti 0x804814e in main () 3: /t $eax = 0 2: /d $eax = 0 1: x/i $eip 0x804814e : movb 0xffffffff(%ebp),%al (gdb) nexti 0x8048151 in main () 3: /t $eax = 1111111 2: /d $eax = 127 1: x/i $eip 0x8048151 : movb %al,0x805e0ec (gdb) nexti 0x8048157 in main () 3: /t $eax = 1111111 2: /d $eax = 127 1: x/i $eip 0x8048157 : movsbl 0x805e0ec,%eax (gdb) nexti 0x804815e in main () 3: /t $eax = 1111111 2: /d $eax = 127 1: x/i $eip 0x804815e : pushl %eax (gdb) nexti 0x804815f in main () 3: /t $eax = 1111111 2: /d $eax = 127 1: x/i $eip 0x804815f : pushl $0x8058838 (gdb) nexti 0x8048164 in main () 3: /t $eax = 1111111 2: /d $eax = 127 1: x/i $eip 0x8048164 : call 0x8048190 <_IO_printf> (gdb) nexti three(-129) = 127 0x8048169 in main () 3: /t $eax = 10010 2: /d $eax = 18 1: x/i $eip 0x8048169 : addl $0x8,%esp (gdb) nexti 0x804816c in main () 3: /t $eax = 10010 2: /d $eax = 18 1: x/i $eip 0x804816c : movb 0xfffffffe(%ebp),%al (gdb) nexti 0x804816f in main () 3: /t $eax = 10000001 2: /d $eax = 129 1: x/i $eip 0x804816f : movb %al,0x805e0ec (gdb) nexti 0x8048175 in main () 3: /t $eax = 10000001 2: /d $eax = 129 1: x/i $eip 0x8048175 : movsbl 0x805e0ec,%eax (gdb) nexti 0x804817c in main () 3: /t $eax = 11111111111111111111111110000001 2: /d $eax = -127 1: x/i $eip 0x804817c : pushl %eax (gdb) nexti 0x804817d in main () 3: /t $eax = 11111111111111111111111110000001 2: /d $eax = -127 1: x/i $eip 0x804817d : pushl $0x805884a (gdb) nexti 0x8048182 in main () 3: /t $eax = 11111111111111111111111110000001 2: /d $eax = -127 1: x/i $eip 0x8048182 : call 0x8048190 <_IO_printf> (gdb) nexti three(129) = -127 0x8048187 in main () 3: /t $eax = 10011 2: /d $eax = 19 1: x/i $eip 0x8048187 : addl $0x8,%esp (gdb) nexti 0x804818a in main () 3: /t $eax = 10011 2: /d $eax = 19 1: x/i $eip 0x804818a : movl %ebp,%esp (gdb) nexti 0x804818c in main () 3: /t $eax = 10011 2: /d $eax = 19 1: x/i $eip 0x804818c : popl %ebp (gdb) nexti 0x804818d in main () 3: /t $eax = 10011 2: /d $eax = 19 1: x/i $eip 0x804818d : ret (gdb) nexti 0x80480ee in ___crt_dummy__ () 3: /t $eax = 10011 2: /d $eax = 19 1: x/i $eip 0x80480ee <___crt_dummy__+94>: pushl %eax (gdb) nexti 0x80480ef in ___crt_dummy__ () 3: /t $eax = 10011 2: /d $eax = 19 1: x/i $eip 0x80480ef <___crt_dummy__+95>: call 0x804d524 (gdb) nexti Program exited with code 023. -128ではオーバーフローにならず,-129ではオーバーフローになるわけ. アセンブラとCのソースを見比べると,Cのソースからアセンブラにコンパイルされる過程で,以下のような変換が行われている. -129 --> 127 "こちらがオーバーフローしているという警告.ただし-128では警告でない" 129 --> -127 -129を代入する. 1. 129 = 0b 1000 0001 2. ビットを全て反転させる.0b 0111 1110 3. 上の数に1を足す.0b 0111 1111 = 127 今の演算に桁上がりはないので,eflagsレジスタのオーバーフローフラグは立たないはず.しかし,signed charだから,MSBを符号ビットとして扱い,eflagsレジスタのSF=1にセットする.したがって,SF=1なのに,-129を表そうとした結果は,0b 0111 1111 = 127でMSBは0で矛盾. -128を代入する. 1. 128 = 0b 1000 0000 2. ビットを全て反転させる.0b 0111 1111 3. 上の数に1を足す.0b 1000 000 = 128 今の演算に桁上がりはないので,eflagsレジスタのオーバーフローフラグは立たないはず.しかし,signed charだから,MSBを符号ビットとして扱い,eflagsレジスタのSF=1にセットする.したがって,SF=1であり且つ負数を求めた結果のMSBも1で矛盾なし. 上の観察をフラグの内容も交えて,sign.sで解決せよ.今のコードではだめ.オート変数では,フラグが変化しないから. ちなみに,char変数に128と129を代入して表示させると,それぞれ-128と-127になる.この時オーバーフローの警告はでない. -129を代入する. 1. 129 = 0b 1000 0001 2. ビットを全て反転させる.0b 0111 1110 3. 上の数に1を足す.0b 0111 1111 = 127 のマイナスだから-127 今の演算に桁上がりはないので,eflagsレジスタのオーバーフローフラグは立たないはず. -255を代入すればオーバーフロー警告が出て,表示は1. 255を代入すればオーバーフロー警告が出ず,表示は-1. 128を代入すればオーバーフロー警告が出ず,表示は-128. -128を代入すればオーバーフロー警告が出ず,表示は-128. 結局,128より大きい数は,全て2の補数で負数にするとMSBが立たなくなる.したがって,恐らくコンパイラは,eflagsのSF=1の時にMSB=0ならばオーバーフローというエラーを出すのであろう.