LoginSignup
2
4

More than 5 years have passed since last update.

avr-gccにおけるbool型の扱い

Posted at

いきなり結論

 AVR 向けのコンパイラ avr-gcc において,bool 型は uint8_t 型 (unsigned char 型) として扱われているようです。

知りたかったこと

 AVR のためのライブラリを作っている中で,uint8_t 型変数の任意のビットを置換する,以下のような関数を書きました。 置換対象の変数 src の offset で指定されたビットが bit で指定した状態に置換されて return されます。

uint8_t ReplaceSingleBit(uint8_t src, int8_t offset, bool bit) {
  assert((0 <= offset) && (offset <= 7));
  return (src & ~(1 << offset)) | (bit << offset);
}

 さてこの関数,とりわけ bool 型の変数 bit はどのようにコンパイルされるでしょうか? もしかして,int8_t とか uint8_t などの整数型を使ったほうが効率よいコードが吐き出されたりする,なーんてことはあるでしょうか?

 そんな疑問が湧いてしまったので,実際どうなるのか調べてみました。

実験

実験に使ったソースコード

 以下のようなソースコードを用意しました。

test.c
#include <stdint.h>
#include <stdbool.h>
#include <assert.h>

uint8_t TestA(uint8_t src, int8_t offset, bool bit) {
  return (src & ~(1 << offset)) | (bit << offset);
}

uint8_t TestB(uint8_t src, int8_t offset, uint8_t bit) {
  return (src & ~(1 << offset)) | (bit << offset);
}

uint8_t TestC(uint8_t src, int8_t offset, int8_t bit) {
  return (src & ~(1 << offset)) | (bit << offset);
}

uint8_t TestD(uint8_t src, int8_t offset, uint16_t bit) {
  return (src & ~(1 << offset)) | (bit << offset);
}

uint8_t TestE(uint8_t src, int8_t offset, int16_t bit) {
  return (src & ~(1 << offset)) | (bit << offset);
}

 TestA は,オリジナルの ReplaceSingleBit 関数と同様の構成です。 その他の関数は引数 bit の型をそれぞれ uint8_t,int8_t,uint16_t,int16_t に変えたものです。 いずれも assert は省略しました。

 で,このコードを,以下のように最適化付きでコンパイルしました。

avr-gcc -S -O2 test.c

得られたアセンブリコード

 結果出力されたのが,以下のアセンブリコードです。

    .file   "test.c"
__SP_H__ = 0x3e
__SP_L__ = 0x3d
__SREG__ = 0x3f
__tmp_reg__ = 0
__zero_reg__ = 1
    .text
.global TestA
    .type   TestA, @function
TestA:
/* prologue: function */
/* frame size = 0 */
/* stack size = 0 */
.L__stack_usage = 0
    ldi r18,lo8(1)
    ldi r19,0
    mov r0,r22
    rjmp 2f
    1:
    lsl r18
    2:
    dec r0
    brpl 1b
    com r18
    and r18,r24
    mov r24,r20
    mov r0,r22
    rjmp 2f
    1:
    lsl r24
    2:
    dec r0
    brpl 1b
    or r24,r18
    ret
    .size   TestA, .-TestA
.global TestB
    .type   TestB, @function
TestB:
/* prologue: function */
/* frame size = 0 */
/* stack size = 0 */
.L__stack_usage = 0
    ldi r18,lo8(1)
    ldi r19,0
    mov r0,r22
    rjmp 2f
    1:
    lsl r18
    2:
    dec r0
    brpl 1b
    com r18
    and r18,r24
    mov r24,r20
    mov r0,r22
    rjmp 2f
    1:
    lsl r24
    2:
    dec r0
    brpl 1b
    or r24,r18
    ret
    .size   TestB, .-TestB
.global TestC
    .type   TestC, @function
TestC:
/* prologue: function */
/* frame size = 0 */
/* stack size = 0 */
.L__stack_usage = 0
    ldi r18,lo8(1)
    ldi r19,0
    mov r0,r22
    rjmp 2f
    1:
    lsl r18
    2:
    dec r0
    brpl 1b
    com r18
    and r18,r24
    mov r24,r20
    clr r25
    sbrc r24,7
    com r25
    mov r0,r22
    rjmp 2f
    1:
    lsl r24
    2:
    dec r0
    brpl 1b
    or r24,r18
    ret
    .size   TestC, .-TestC
.global TestD
    .type   TestD, @function
TestD:
/* prologue: function */
/* frame size = 0 */
/* stack size = 0 */
.L__stack_usage = 0
    ldi r18,lo8(1)
    ldi r19,0
    mov r0,r22
    rjmp 2f
    1:
    lsl r18
    2:
    dec r0
    brpl 1b
    com r18
    and r18,r24
    mov r0,r22
    rjmp 2f
    1:
    lsl r20
    2:
    dec r0
    brpl 1b
    mov r24,r18
    or r24,r20
    ret
    .size   TestD, .-TestD
.global TestE
    .type   TestE, @function
TestE:
/* prologue: function */
/* frame size = 0 */
/* stack size = 0 */
.L__stack_usage = 0
    ldi r18,lo8(1)
    ldi r19,0
    mov r0,r22
    rjmp 2f
    1:
    lsl r18
    2:
    dec r0
    brpl 1b
    com r18
    and r18,r24
    mov r0,r22
    rjmp 2f
    1:
    lsl r20
    2:
    dec r0
    brpl 1b
    mov r24,r18
    or r24,r20
    ret
    .size   TestE, .-TestE
    .ident  "GCC: (AVR_8_bit_GNU_Toolchain_3.4.5_1522) 4.8.1"

アセンブリコードの比較

 それぞれのアセンブリコードを比べてみます。

bool uint8_t int8_t uint16_t int16_t
TestA: TestB: TestC: TestD: TestE:
.L__stack_usage = 0 .L__stack_usage = 0 .L__stack_usage = 0 .L__stack_usage = 0 .L__stack_usage = 0
ldi r18,lo8(1) ldi r18,lo8(1) ldi r18,lo8(1) ldi r18,lo8(1) ldi r18,lo8(1)
ldi r19,0 ldi r19,0 ldi r19,0 ldi r19,0 ldi r19,0
mov r0,r22 mov r0,r22 mov r0,r22 mov r0,r22 mov r0,r22
rjmp 2f rjmp 2f rjmp 2f rjmp 2f rjmp 2f
1: 1: 1: 1: 1:
lsl r18 lsl r18 lsl r18 lsl r18 lsl r18
2: 2: 2: 2: 2:
dec r0 dec r0 dec r0 dec r0 dec r0
brpl 1b brpl 1b brpl 1b brpl 1b brpl 1b
com r18 com r18 com r18 com r18 com r18
and r18,r24 and r18,r24 and r18,r24 and r18,r24 and r18,r24
mov r24,r20 mov r24,r20 mov r24,r20
clr r25
sbrc r24,7
com r25
mov r0,r22 mov r0,r22 mov r0,r22 mov r0,r22 mov r0,r22
rjmp 2f rjmp 2f rjmp 2f rjmp 2f rjmp 2f
1: 1: 1: 1: 1:
lsl r24 lsl r24 lsl r24 lsl r20 lsl r20
2: 2: 2: 2: 2:
dec r0 dec r0 dec r0 dec r0 dec r0
brpl 1b brpl 1b brpl 1b brpl 1b brpl 1b
or r24,r18 or r24,r18 or r24,r18 or r24,r18 or r24,r18
ret ret ret ret ret
.size TestA, .-TestA .size TestB, .-TestB .size TestC, .-TestC .size TestD, .-TestD .size TestE, .-TestE

 結果からすると,引数の型を bool としたときに出力されるコードは,引数の型を uint8_t とまったく同じになりました。 というわけで,冒頭の結論に至ったわけです。

 ちょっと意外だったのが,int8_t 型の時の挙動。 引数の型を int8_t にした時だけ,r20 (引数 bit の値) を r24 にコピーした後でご丁寧に r25 の符号処理を行っています。 後の処理では使われないのに……?

 また,16 bit 整数型を使うと,bool 型や 8 bit 整数型を使った場合と違って,r20 (引数 bit の値) を r24 へコピーする処理がありません。 結果,関数内の命令数が一番少ないのは 16 bit 整数型を使った場合となりました。 しかしながら,この関数は bit の値が 0 か 1 でないと期待した動作をしてくれません。 ので,引数 bit の型を bool 以外にした場合,値が 0 または 1 であることのアサーションが必要になります。 こういう点まで考えると,引数 bit の型には bool を選択するのがよさそうです。

 とりあえずの興味は満たされたので,今のところはこれ以上深追いはしない予定です。

2
4
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4