人が書いたプログラムを見ていたとき、switch文のcase句の書き方に違和感があるコードが有りました。
要点だけ抜粋すると、以下の内容です。
switch( data ) { ... case'1': // <- caseと'1'の間に空白がない!! ... break; case'2': ... break; default: ... break; } |
caseと条件値の間にはスペースが必要だと思ってたのですが、上記のコードはどうやらコンパイルも正常に行え、振る舞いも想定どおりな様です。
気になったので、このへんの振る舞いに関する確認用プログラムを作成してみました。
#include <stdio.h> #define CS4 '4' int main() { int data; printf( "please input data>" ); scanf( "%d", &data ); switch( data ) { case 0: printf( "pos0\n" ); break; case1: // 空白を入れない printf( "pos1\n" ); break; case '2': // '2' = 0x32 = 50 printf( "pos2\n" ); break; case'3': // '3' = 0x33 = 51 printf( "pos3\n" ); break; caseCS4: // define値で空白を入れない printf( "pos4\n" ); break; case0x05: // 16進数で空白を入れない printf( "pos5\n" ); break; default: printf( "default\n" ); break; } } |
実行結果:
$ gcc switch_test.c -o switch_test $ ./switch_test please input data>0 pos0 $ ./switch_test please input data>1 default $ ./switch_test please input data>50 pos2 $ ./switch_test please input data>51 pos3 $ ./switch_test please input data>52 default $ ./switch_test please input data>5 default |
各パターンの動作結果に対して、なぜそうなったのかを考えて見ます
case 0
これは正常パターンなので、当然動作します。
case1:
"case1"というgotoラベルと解釈されるので、正しく動作しません。
後述しますが、アセンブラの結果を見ても"pos1"の出力処理自体が省かれています。
case '2':
これも正常パターン。動作します。
case'3':
これが当初違和感を感じたパターンです。
gccでコンパイルしましたが、はやり正常に動作してます。
caseCS4:
コードを書いてたときは、このパターンはいけるのでは...と思ってたのですが、NGでした。
良く考えてみたら、"caseCS4"というgotoラベルと認識されるだけなので、ダメで当然ですね。
case0x05:
これもダメでした。 |
で、こちらがアセンブル出力です。
pos1, pos4, pos5に関しては、処理自体が無かったものと見なされてます(コードまで追わなくても.LCxの定数部だけ見れば一目瞭然です)。
ジャンプされる可能性がないgotoラベルと見なされたからだと思われます。
$ gcc switch_test.c -S
$ cat switch_test.s
.file "switch_test.c"
.section .rodata
.LC0:
.string "please input data>"
.LC1:
.string "%d"
.LC2:
.string "pos0"
.LC3:
.string "pos2"
.LC4:
.string "pos3"
.LC5:
.string "default"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
andl $-16, %esp
subl $32, %esp
movl $.LC0, %eax
movl %eax, (%esp)
call printf
movl $.LC1, %eax
leal 28(%esp), %edx
movl %edx, 4(%esp)
movl %eax, (%esp)
call __isoc99_scanf
movl 28(%esp), %eax
cmpl $50, %eax
je .L4
cmpl $51, %eax
je .L5
testl %eax, %eax
jne .L7
.L3:
movl $.LC2, (%esp)
call puts
jmp .L8
.L4:
movl $.LC3, (%esp)
call puts
jmp .L8
.L5:
movl $.LC4, (%esp)
call puts
jmp .L8
.L7:
movl $.LC5, (%esp)
call puts
nop
.L8:
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1"
.section .note.GNU-stack,"",@progbits |
というわけで、直感に反する振る舞いは”caseの後に空白をおかずに文字定数が続く”パターンだけでした。
この振る舞いになる原因をJIS X3010の規格書から調べてみたのですが、分かりませんでした…
なぜだろう??
関連記事
コメントを残す