概要
ふとしたことでC言語のenumの挙動を調べていて気付いたことです。
enumの値設定の使用には注意が必要だということに気付きました。
環境
$ cat /etc/os-release
PRETTY_NAME="Ubuntu 24.04 LTS"
NAME="Ubuntu"
VERSION_ID="24.04"
VERSION="24.04 LTS (Noble Numbat)"
VERSION_CODENAME=noble
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=noble
LOGO=ubuntu-logo
$ gcc --version
gcc (Ubuntu 13.2.0-23ubuntu4) 13.2.0
Copyright (C) 2023 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ clang --version
Ubuntu clang version 18.1.3 (1ubuntu1)
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
内容
enum_test.c
#include <stdio.h>
typedef enum enum_test
{
Enum1_a,
Enum1_b,
Enum2_a = 5,
Enum2_b,
} EnumTest;
char *PrintEnumName(EnumTest Val)
{
if (Val == Enum1_a)
{
return "Enum1_a";
}
if (Val == Enum1_b)
{
return "Enum1_b";
}
if (Val == Enum2_a)
{
return "Enum2_a";
}
if (Val == Enum2_b)
{
return "Enum2_b";
}
return ("Not Assigned Enum");
}
void PrintEnumTest(EnumTest Val)
{
printf("Enum: %s : %d\n", PrintEnumName(Val), Val);
}
int main(int argc, char **argv)
{
PrintEnumTest(Enum1_a);
PrintEnumTest(Enum1_b);
PrintEnumTest(Enum2_a);
PrintEnumTest(Enum2_b);
return 0;
}
結果は
$ gcc -Wall enum_test.c -o enum_test
$ ./enum_test
Enum : Enum1_a : 0
Enum : Enum1_b : 1
Enum : Enum2_a : 5
Enum : Enum2_b : 6
想定通り
ちょっと長いですが
enum_test2.c
#include <stdio.h>
typedef enum enum_test
{
Enum1_a,
Enum1_b,
Enum1_c,
Enum1_d,
Enum1_e,
Enum1_f,
Enum2_a = 5,
Enum2_b,
} EnumTest;
char *PrintEnumName(EnumTest Val)
{
if (Val == Enum1_a)
{
return "Enum1_a";
}
if (Val == Enum1_b)
{
return "Enum1_b";
}
if (Val == Enum1_c)
{
return "Enum1_c";
}
if (Val == Enum1_d)
{
return "Enum1_d";
}
if (Val == Enum1_e)
{
return "Enum1_e";
}
if (Val == Enum1_f)
{
return "Enum1_f";
}
if (Val == Enum2_a)
{
return "Enum2_a";
}
if (Val == Enum2_b)
{
return "Enum2_b";
}
return ("Not Assigned Enum");
}
void PrintEnumTest(EnumTest Val)
{
printf("Enum: %s : %d\n", PrintEnumName(Val), Val);
}
int main(int argc, char **argv)
{
PrintEnumTest(Enum1_a);
PrintEnumTest(Enum1_b);
PrintEnumName(Enum1_c);
PrintEnumTest(Enum1_d);
PrintEnumTest(Enum1_e);
PrintEnumTest(Enum1_f);
PrintEnumTest(Enum2_a);
PrintEnumTest(Enum2_b);
return 0;
}
実行結果
$ gcc -Wall enum_test2.c -o enum_test2
$ ./enum_test2
Enum: Enum1_a : 0
Enum: Enum1_b : 1
Enum: Enum1_d : 3
Enum: Enum1_e : 4
Enum: Enum1_f : 5
Enum: Enum1_f : 5
Enum: Enum2_b : 6
特にワーニングなどなく実行できてしまう。
PrintEnumTest(Enum2_a);
の処理が
PrintEnumTest(Enum1_f);
として実行されています。
if (Val == Enum2_a)
{
return "Enum2_a";
}
は実行されていないことになります。
enum_test3.c
#include <stdio.h>
typedef enum enum_test
{
Enum1_a,
Enum1_b,
Enum1_c,
Enum1_d,
Enum1_e,
Enum1_f,
Enum2_a = 5,
Enum2_b,
} EnumTest;
char *PrintEnumName(EnumTest Val)
{
switch (Val) {
case Enum1_a: return "Enum1_a";
case Enum1_b: return "Enum1_b";
case Enum1_c: return "Enum1_c";
case Enum1_d: return "Enum1_d";
case Enum1_e: return "Enum1_e";
case Enum1_f: return "Enum1_f";
case Enum2_a: return "Enum2_a";
case Enum2_b: return "Enum2_b";
default: return ("Not Assigned Enum");
}
}
void PrintEnumTest(EnumTest Val)
{
printf("Enum: %s : %d\n", PrintEnumName(Val), Val);
}
int main(int argc, char **argv)
{
PrintEnumTest(Enum1_a);
PrintEnumTest(Enum1_b);
PrintEnumName(Enum1_c);
PrintEnumTest(Enum1_d);
PrintEnumTest(Enum1_e);
PrintEnumTest(Enum1_f);
PrintEnumTest(Enum2_a);
PrintEnumTest(Enum2_b);
return 0;
}
switch文を使ったら
$ gcc -Wall enum_test3.c -o enum_test3
enum_test3.c: In function ‘PrintEnumName’:
enum_test3.c:23:9: error: duplicate case value
23 | case Enum2_a: return "Enum2_a";
| ^~~~
enum_test3.c:22:9: note: previously used here
22 | case Enum1_f: return "Enum1_f";
| ^~~~
ちゃんと怒られたけどenumの値を問題にしているわけではなくてswitch文の条件に被りがあるとエラーを出している。
ちなみにdefault:をなくしてswitchの中にすべてのenumをすべて書かなかったら
warning: enumeration value ‘xxxxx’ not handled in switch [-Wswitch]
と指摘してくれます。
誰も気にしていないのか?
一応気にしていいる人はいるらしい。
clangを使えばワーニングを出してくれる。
$ clang enum_test2.c -Wduplicate-enum -o tmp2
enum_test2.c:10:2: warning: element 'Enum1_f' has been implicitly assigned 5 which another element has been assigned [-Wduplicate-enum]
10 | Enum1_f,
| ^~~~~~~
enum_test2.c:11:2: note: element 'Enum2_a' also has value 5
11 | Enum2_a = 5,
| ^~~~~~~~~~~
1 warning generated.
まとめ
gccではenum listが少なければ可能な限りswitchで処理するのが正しいと思われます。
listが膨大になったらひたすら気を付けるしかないということです。
enumの値がdefineで宣言された値とかbitでorとか取られていると確認がとても大変そうですが…。