列挙型(enum)

列挙定数は名前を並べることで作る整数定数のリストで、明示的に値を指定しない時は並べた名前の順に0から始まって1ずつ大きくなる値を自動的に割り振ってくれる。

enum01.c

#include <stdio.h>

enum cardsuit {
  CLUBS,
  DIAMONDS,
  HEARTS,
  SPADES
};

int main()
{
  printf( "CLUBS = %d?n", CLUBS);
  printf( "DIAMONDS = %d?n", DIAMONDS);
  printf( "HEARTS = %d?n", HEARTS);
  printf( "SPADES = %d?n", SPADES);
}

コンパイルして実行してみると自動的に値が付けられていることが分かる。

$./enum01
CLUBS = 0
DIAMONDS = 1
HEARTS = 2
SPADES = 3

それにしても考えてみると、列挙定数を宣言した時に付けた識別子cardsuitという名前はプログラム中では特に使っていない。宣言する時に書いただけだ。コンパイラが定数値を自動的に割り振るための範囲を明確にするために名前を付けるのかなぁ。まぁ人がプログラムを見た時の可読性も高くなるっていうのもあるか。

enumを使って定数を作るだけではなく列挙型の変数というのも作れるらしい。

enum02.c


enum cardsuit {
  CLUBS,
  DIAMONDS,
  HEARTS,
  SPADES
};

int main()
{
  enum cardsuit suit;

  suit = HEARTS;

  printf( "suit = %d?n", suit);
}

実行して変数suitの中身を見てみると、ちゃんとHEARTSの実際の値2が入っていることが分かる。

$./enum02 
suit = 2

もしかして、列挙変数を使うと変数の値の範囲チェックをコンパイラが実行してくれるのかも!?と期待して実験してみた。

#include <stdio.h>

enum cardsuit {
  CLUBS,
  DIAMONDS,
  HEARTS,
  SPADES
};

enum boolean {
  BOOL_NO,
  BOOL_YES
};

int main()
{
  enum cardsuit suit;
  enum boolean bool;

  bool = SPADES;

  printf( "bool = %d?n", bool);
}

enum boolean型の変数は0か1の値しかないはずなので、enum boolean型変数にSPADES(=3)を代入するとコンパイルエラーになるのではないだろか、などと思いながらコンパイルしてみたが、コンパイルは何の問題もなく終了し普通に実行できた…。

$gcc -o enum03 enum03.c; echo $?
0
$./enum03 
bool = 3

「C&R 第二版」に「enum型の変数も宣言できるが、コンパイラでは、そうした変数に代入される値が列挙にとって正しいかどうかのチェックはする必要がない」と記述されている通りらしい。ただコンパイラの実装によってはチェックすることも可能だろうから、プリプロセッサで処理される#defineとはやはり違うのであろう。ちなみに今回の実験に使っているOSはPowerPCMac OS Xで、gccはバージョン4。

$uname -m -s -r
Darwin 8.9.0 Power Macintosh
$gcc -dumpversion
4.0.0

実際に#defineで定数を定義したものとenumを使ったものではどれぐらい違うのだろうか?と疑問に思ったので二つのプログラムを書いて比較してみた。

enum04.c

#include <stdio.h>

enum boolean {
  BOOL_NO,
  BOOL_YES
};

int main()
{
  enum boolean bool = BOOL_YES;

  printf( "bool = %d?n", bool);
}
enum05.c

#include <stdio.h>

#define BOOL_NO 0
#define BOOL_YES 1

int main()
{
  int bool = BOOL_YES;

  printf( "bool = %d?n", bool);
}

二つのプログラムをコンパイルして実行してみると実行結果は当然同じ。

$./enum04
bool = 1
$./enum05
bool = 1

二つの実行ファイルの大きさを比べてみても同じ…。

$wc -c enum0[45]
   17204 enum04
   17204 enum05
   34408 total

cmpコマンドを使ってバイナリ比較したところ、違いがない。

$cmp enum0[45]
$

うーん…普通にコンパイルすると、enumと#defineには違いが出ないようだ。何か不思議。

#defineで定義された値はプリプロセッサで処理されるので、プリプロセッサ処理直後の結果を比べてみると…何と異なっている!当たり前なんだけれども。

$gcc -E enum04.c > enum04.i
$gcc -E enum05.c > enum05.i
$diff enum0[45].i
411,414d412
< enum boolean {
<   BOOL_NO,
<   BOOL_YES
< };
418c416
<   enum boolean bool = BOOL_YES;
---
>   int bool = 1;

※長くなるので中略している。

「エキスパートCプログラミング」でも「列挙型の名前はデバッガまで生き残るため、デバッグ中にも利用できるのだ」と列挙型の長所が語られている。デバッガを使うことまで視野に入れると違いが見えてくるということか。

gオプション付きでそれぞれのプログラムをコンパイルして比べてみると、大きさや中身が違うことが分かる。

$gcc -g -o enum04g enum04.c
$gcc -g -o enum05g enum05.c

$wc -c enum0[45]g
   17568 enum04g
   17508 enum05g
   35076 total

$cmp enum0[45]g
enum04g enum05g differ: char 984, line 2

#defineを使ったenum05gをdbgで実行してみると、変数の中身を表示させた時にマクロ名ではなく数値になっていることが分かる。

$gdb enum05g
(gdb) break main
Breakpoint 1 at 0x2afc: file enum05.c, line 8.
(gdb) run
Starting program: enum05g 
Breakpoint 1, main () at enum05.c:8
8         int bool = BOOL_YES;
(gdb) stepi
0x00002b00      8         int bool = BOOL_YES;
(gdb) stepi
10        printf( "bool = %d?n", bool);
(gdb) print bool
$1 = 1

※長くなるので各所で中略している。

同様にしてenum版に対してdbgを使ってみると確かに変数表示を行った時に、enumで定義した定数の名前が出てくる。

$gdb enum04g
(gdb) break main
Breakpoint 1 at 0x2afc: file enum04.c, line 10.
(gdb) run
Starting program: enum04g 
Breakpoint 1, main () at enum04.c:10
10        enum boolean bool = BOOL_YES;
(gdb) stepi
0x00002b00      10        enum boolean bool = BOOL_YES;
(gdb) stepi
12        printf( "bool = %d?n", bool);
(gdb) print bool
$1 = BOOL_YES

※長くなるので各所で中略している。

列挙定数として宣言した定数を列挙型変数ではなく普通のint型変数で使ってみる。

#include <stdio.h>

enum boolean {
  BOOL_NO,
  BOOL_YES
};

int main()
{
  int bool = BOOL_YES;

  printf( "bool = %d?n", bool);
}

この場合はデバッガで変数の中身を表示しても、定数の名前ではなく数値で表示されてしまう。gdbはその変数の型を認識し、その型に応じた値を表示してくれるようだ。enum型定数を定義した時は同じenum型の変数で用いろ、ということなんだろうな。

$gdb enum06g
(gdb) break main
Breakpoint 1 at 0x2afc: file enum06.c, line 10.
(gdb) run
Starting program: enum06g 
Breakpoint 1, main () at enum06.c:10
10        int bool = BOOL_YES;
(gdb) stepi
0x00002b00      10        int bool = BOOL_YES;
(gdb) stepi
12        printf( "bool = %d?n", bool);
(gdb) print bool
$1 = 1

※長くなるので各所で中略している。