整数拡張と算術型変換をコンパイル結果で見てみる

C99 の整数拡張と算術型変換の「見える化」ってできないか〜と思い、何か境界条件などで整数拡張や算術型変換の有無が結果に表れてくる例がないか?と考えたけど思いつかなかった。gccコンパイル結果(アセンブリコード)を見ても IA-32 だと、

  • 負の数は2の補数表現になるため、int + int も unsigned int + unsigned int も addl 命令が利用される
  • 積算については int * int も、 unsigned int * unsigned int も 32 * 32 -> 64 bit の imul を利用される

となるため整数拡張なのか算術型変換なのか実装理由なのか判断できない。なんとか取り扱う正負やバイト幅が命令に反映されるものは何かと考えた結果、除算 div で観察してみた。

int / signed char

int main(int argc, char** argv) {
    int n = 10;
    char sc = -1;
 
    return n / sc;
}
  • 整数拡張
    • n はそのまま、sc は signed char -> int となるはず
  • 算術型変換
    • int と int は同順位で同符号のため、int / int で計算されるはず

実際に、

       movsbl  -5(%ebp),%eax           ; sc: signed char -> int への符号拡張による整数拡張
       movl    -12(%ebp), %edx
       movl    %eax, %ecx
       movl    %edx, %eax
       sarl    $31, %edx
       idivl   %ecx                    ; sc を int として符号付き除算を計算している

unsigned int / signed char

int main(int argc, char** argv) {
    unsigned int n = 10;
    char sc = -1;
 
    return n / sc;
}
  • 整数拡張
    • n はそのまま、signed char -> int となるはず
  • 算術型変換
    • int と unsigned int は同順位だが符号が違うため、int が unsigned int に算術型変換されて、unsigned int / unsigned int で計算されるはず

実際に、

       movsbl  -5(%ebp),%edx           ; sc: signed char -> int への符号拡張による整数拡張
       movl    -12(%ebp), %eax
       movl    %edx, %ecx
       movl    $0, %edx
       divl    %ecx                    ; sc を unsigned int として符号無し除算で扱っている

long / signed char

int main(int argc, char** argv) {
    long n = 10;
    char sc = -1;
 
    return n / sc;
}
  • 整数拡張
    • n はそのまま、sc は signed char -> int となるはず
  • 算術型変換
    • int < long という順位のため、int が long に算術型変換されて、long / long で計算されるはず

実際に、

       movsbl  -5(%ebp),%eax           ; sc: signed char -> int への符号拡張による整数拡張
       movl    -12(%ebp), %edx
       movl    %eax, %ecx
       movl    %edx, %eax
       sarl    $31, %edx
       idivl   %ecx                    ; sc を int = long として符号付き除算を計算している
                                       ; → signed char / int のケースと同じ

short / signed char

int main(int argc, char** argv) {
    short n = 10;
    char sc = -1;
 
    return n / sc;
}
  • 整数拡張
    • n は short -> int, sc は signed char -> int となるはず
  • 算術型変換
    • int と int は同順位で同符号のため、int / int で計算されるはず

実際に、

       movswl  -8(%ebp),%edx           ; n: short -> int への符号拡張による整数拡張
       movsbl  -5(%ebp),%eax           ; sc: signed char -> int への符号拡張による整数拡張
       movl    %eax, %ecx
       movl    %edx, %eax
       sarl    $31, %edx
       idivl   %ecx                    ; sc を int として符号付き除算を計算している

unsigned short / signed char

int main(int argc, char** argv) {
    unsigned short n = 10;
    char sc = -1;
 
    return n / sc;
}
  • 整数拡張
    • unsigned short の全ての値は int で表せるため、n は unsigned short -> int, sc は signed char -> int となるはず
  • 算術型変換
    • int と int は同順位で同符号のため、int / int で計算されるはず

実際に、

       movzwl  -8(%ebp), %edx          ; n: unsigned short -> int への 0 拡張による整数拡張 (0 拡張なのがポイント)
       movsbl  -5(%ebp),%eax           ; sc: signed char -> int への符号拡張による整数拡張
       movl    %eax, %ecx
       movl    %edx, %eax
       sarl    $31, %edx
       idivl   %ecx                    ; sc を int として符号付き除算を計算している

ばっちり変換が見えたわけではないが、なんとなく形跡があったので満足。ただ整数拡張の意義がよくわからない。算術型変換は型が違う場合にどう計算するのか手順を定義しているので分かりやすいが、整数拡張って必要なのかな?例えば、long / signed char を、整数拡張で signed char -> int と拡張されてから算術型変換が適応されて int -> long に変換され、long / long になったと定義通りに考えてもいいけど、仮に整数拡張がなく算術型変換で signed char -> long と変換されて long / long になったと考えても同じ結果になる。算術型変換だけでも十分な気がするけど、なぜ整数拡張は定義されているのだろう。(まさか「レジスタに代入する」というマシン語上の行為を規約で追認したようなものではないよなぁ)