見出し画像

何故 ARM だと char が unsigned になるのか

タイトル通りです。

signed char ではなく
unsigned char でもなく
プレーンで char 型を使ったときに
何故 ARM だと unsigned で展開されるのか

従来、C言語では、
unsigned も signed も指定しない場合、 signed である
というのが一般的でした。

unsignedsigned も指定しないプレーンな char を使用した場合、多くのC言語プログラマーは「signed char」と考える人が多いでしょう。しかしながら、C言語の言語仕様としては、「char」が「signed char」であると明確に規定されているわけではないそうです。ですが、規定されていないとは言え「符号を指定しない場合は有符号(signed)」に慣れているプログラマーが多い中で、何故わざわざ「無符号(unsigned)」にする必要があるのか。それが甚だ疑問でした。

それを調べてみました。

そもそもの発端は、前回記事のこちらにあります。


結論から申しますと・・・。

正確にはわからなかったorz。

で終わるのもアレなので、とにかくスッタモンダした経緯を書いておこうかと思います。

とりあえず、
char の場合と
signed char の場合で
それぞれどのようにアセンブラに展開されるのか見てみました。

ソースコードはこちら。

void clear(char data[])
{
    char c;
    for (c = 0; c < 0x80; c++)
    {
        data[c] = 0;
    }
}
void clear_s(signed char data[])
{
    signed char c;
    for (c = 0; c < 0x80; c++)
    {
        data[c] = 0;
    }
}

そして、アセンブラです。
関数「clear_s」のアセンブラは、
関数「clear」との差異のみ記載します。

clear:                                  // %bb.0:
	sub	sp, sp, #16
	.cfi_def_cfa_offset 16
	str	x0, [sp, #8]
	strb	wzr, [sp, #7]
	b	.LBB0_1
.LBB0_1:                                // =>This Inner Loop Header: Depth=1
	ldrb	w8, [sp, #7]
	subs	w8, w8, #128
	cset	w8, ge
	tbnz	w8, #0, .LBB0_4
	b	.LBB0_2
.LBB0_2:                                //   in Loop: Header=BB0_1 Depth=1
	ldr	x8, [sp, #8]
	ldrb	w9, [sp, #7]
                                        // kill: def $x9 killed $w9
	add	x8, x8, x9
	strb	wzr, [x8]
	b	.LBB0_3
.LBB0_3:                                //   in Loop: Header=BB0_1 Depth=1
	ldrb	w8, [sp, #7]
	add	w8, w8, #1
	strb	w8, [sp, #7]
	b	.LBB0_1
.LBB0_4:
	add	sp, sp, #16
	.cfi_def_cfa_offset 0
	ret
clear_s:                                .LBB1_1:                                // =>This Inner Loop Header: Depth=1
	ldrsb	w8, [sp, #7]
	subs	w8, w8, #128
	cset	w8, ge
	tbnz	w8, #0, .LBB1_4
	b	.LBB1_2
.LBB1_2:                                //   in Loop: Header=BB1_1 Depth=1
	ldr	x8, [sp, #8]
	ldrsb	x9, [sp, #7]
	add	x8, x8, x9
	strb	wzr, [x8]
	b	.LBB1_3

それで、結局のところ、どこがどう違うのかというと、差違はここだけ。

ldrb	w8, [sp, #7]
ldrsb	w8, [sp, #7]

メモリ [sp, #7] から 1byte のデータをレジスタにロードする命令です。

ARM には 1byte のレジスタはありません。

  • レジスタ x864bit

  • レジスタ w832bit

w8x8 の半分だけを使います。

8bit から 32bit に展開する場合、残りの 24bit をどうするのかを考えなければなりません。

特に、 8bit 全てが 1 で、
1111 1111
の場合、
255 と解釈するのか、はたまた
-1 と解釈するのか

(1)255 の場合
0000 0000 0000 0000 0000 0000 1111 1111
に展開しなければなりません。

そして。

(2)-1 の場合
1111 1111 1111 1111 1111 1111 1111 1111
に展開しなければなりません。

先のニーモニックで
ldrb は (1) のように展開して、
ldrsb は (2) のように展開します。

それだけ?
それだけ。
と言っても、それぞれの命令の処理時間までは考慮していないのですが。

ですが、もう少しだけ。

次のサイトは、 ARM でよくお世話なっているサイトです。

中央あたりに表があるのですが、ロード命令がなんと、9種類もあるというのです。

符号付きは 7種類を使い分けなければならないのですが、
符号なしであれば 5種類で済む。

うーん。

このためかなぁ。

「符号付きの方が、アセンブラに展開するときに面倒」?
コンピューターにとってそんなことが問題になるだろうか。

イマイチしっくりこない。
全く違う理由かもしれない。

ご存知の方がいらっしゃれば教えていただけると幸いです。

という、中途半端なオチでした・・・

この記事が気に入ったらサポートをしてみませんか?