C言語　enumで値指定は注意が必要

## 概要
ふとしたことでC言語のenumの挙動を調べていて気付いたことです。
enumの値設定の使用には注意が必要だということに気付きました。
## 環境
```shell
$ 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

```
## 内容
```C: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;
}
```
結果は
```Shell
$ 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
```
想定通り
ちょっと長いですが
```C: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;
}
```
実行結果
```shell
$ 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

```
特にワーニングなどなく実行できてしまう。
```C
PrintEnumTest(Enum2_a);
```
の処理が
```C
PrintEnumTest(Enum1_f);
```
として実行されています。
```C
if (Val == Enum2_a)
	{
		return "Enum2_a";
	}
 ```
 は実行されていないことになります。

```C: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文を使ったら
```Shell
$ 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をすべて書かなかったら
```shell
warning: enumeration value ‘xxxxx’ not handled in switch [-Wswitch]
```
と指摘してくれます。

## 誰も気にしていないのか？
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=16186

一応気にしていいる人はいるらしい。
clangを使えばワーニングを出してくれる。
```shell
$ 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とか取られていると確認がとても大変そうですが…。