Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > comp.lang.c > #394647 > unrolled thread
| Started by | pozz <pozzugno@gmail.com> |
|---|---|
| First post | 2025-10-22 10:56 +0200 |
| Last post | 2025-10-22 18:13 +0000 |
| Articles | 20 on this page of 38 — 11 participants |
Back to article view | Back to comp.lang.c
bugprone-switch-missing-default-case pozz <pozzugno@gmail.com> - 2025-10-22 10:56 +0200
Re: bugprone-switch-missing-default-case Janis Papanagnou <janis_papanagnou+ng@hotmail.com> - 2025-10-22 11:32 +0200
Re: bugprone-switch-missing-default-case Richard Harnden <richard.nospam@gmail.invalid> - 2025-10-22 12:44 +0100
Re: bugprone-switch-missing-default-case Thiago Adams <thiago.adams@gmail.com> - 2025-10-22 10:05 -0300
Re: bugprone-switch-missing-default-case Kaz Kylheku <643-408-1753@kylheku.com> - 2025-10-22 18:22 +0000
Re: bugprone-switch-missing-default-case David Brown <david.brown@hesbynett.no> - 2025-10-23 09:12 +0200
Re: bugprone-switch-missing-default-case Thiago Adams <thiago.adams@gmail.com> - 2025-10-23 08:03 -0300
Re: bugprone-switch-missing-default-case David Brown <david.brown@hesbynett.no> - 2025-10-23 17:06 +0200
Re: bugprone-switch-missing-default-case Thiago Adams <thiago.adams@gmail.com> - 2025-10-23 13:15 -0300
Re: bugprone-switch-missing-default-case bart <bc@freeuk.com> - 2025-10-23 17:46 +0100
Re: bugprone-switch-missing-default-case Thiago Adams <thiago.adams@gmail.com> - 2025-10-23 14:12 -0300
Re: bugprone-switch-missing-default-case David Brown <david.brown@hesbynett.no> - 2025-10-23 22:40 +0200
Re: bugprone-switch-missing-default-case scott@slp53.sl.home (Scott Lurndal) - 2025-10-23 20:51 +0000
Re: bugprone-switch-missing-default-case Keith Thompson <Keith.S.Thompson+u@gmail.com> - 2025-10-23 16:21 -0700
Re: bugprone-switch-missing-default-case David Brown <david.brown@hesbynett.no> - 2025-10-24 08:59 +0200
Re: bugprone-switch-missing-default-case scott@slp53.sl.home (Scott Lurndal) - 2025-10-24 15:51 +0000
Re: bugprone-switch-missing-default-case tTh <tth@none.invalid> - 2025-10-23 23:57 +0200
Re: bugprone-switch-missing-default-case David Brown <david.brown@hesbynett.no> - 2025-10-24 09:22 +0200
Re: bugprone-switch-missing-default-case antispam@fricas.org (Waldek Hebisch) - 2025-10-23 23:23 +0000
Re: bugprone-switch-missing-default-case David Brown <david.brown@hesbynett.no> - 2025-10-24 09:53 +0200
Re: bugprone-switch-missing-default-case David Brown <david.brown@hesbynett.no> - 2025-10-22 15:41 +0200
Re: bugprone-switch-missing-default-case Janis Papanagnou <janis_papanagnou+ng@hotmail.com> - 2025-10-22 16:05 +0200
Re: bugprone-switch-missing-default-case David Brown <david.brown@hesbynett.no> - 2025-10-22 17:23 +0200
Re: bugprone-switch-missing-default-case Janis Papanagnou <janis_papanagnou+ng@hotmail.com> - 2025-10-23 04:54 +0200
Re: bugprone-switch-missing-default-case David Brown <david.brown@hesbynett.no> - 2025-10-23 09:30 +0200
Re: bugprone-switch-missing-default-case antispam@fricas.org (Waldek Hebisch) - 2025-10-23 22:40 +0000
Re: bugprone-switch-missing-default-case Keith Thompson <Keith.S.Thompson+u@gmail.com> - 2025-10-22 12:41 -0700
Re: bugprone-switch-missing-default-case pozz <pozzugno@gmail.com> - 2025-10-23 08:47 +0200
Re: bugprone-switch-missing-default-case Keith Thompson <Keith.S.Thompson+u@gmail.com> - 2025-10-23 16:17 -0700
Re: bugprone-switch-missing-default-case Keith Thompson <Keith.S.Thompson+u@gmail.com> - 2025-10-23 19:13 -0700
Re: bugprone-switch-missing-default-case David Brown <david.brown@hesbynett.no> - 2025-10-23 10:44 +0200
Re: bugprone-switch-missing-default-case Janis Papanagnou <janis_papanagnou+ng@hotmail.com> - 2025-10-22 15:56 +0200
Re: bugprone-switch-missing-default-case David Brown <david.brown@hesbynett.no> - 2025-10-22 17:25 +0200
Re: bugprone-switch-missing-default-case Janis Papanagnou <janis_papanagnou+ng@hotmail.com> - 2025-10-23 04:39 +0200
Re: bugprone-switch-missing-default-case David Brown <david.brown@hesbynett.no> - 2025-10-23 09:08 +0200
Re: bugprone-switch-missing-default-case Keith Thompson <Keith.S.Thompson+u@gmail.com> - 2025-10-23 16:31 -0700
Re: bugprone-switch-missing-default-case David Brown <david.brown@hesbynett.no> - 2025-10-24 10:09 +0200
Re: bugprone-switch-missing-default-case Kaz Kylheku <643-408-1753@kylheku.com> - 2025-10-22 18:13 +0000
Page 1 of 2 [1] 2 Next page →
| From | pozz <pozzugno@gmail.com> |
|---|---|
| Date | 2025-10-22 10:56 +0200 |
| Subject | bugprone-switch-missing-default-case |
| Message-ID | <10da67g$3q59f$1@dont-email.me> |
From here[1]:
> Switch statements without a default case can lead to unexpected
> behavior and incomplete handling of all possible cases. When a switch
> statement lacks a default case, if a value is encountered that does
> not match any of the specified cases, the program will continue
> execution without any defined behavior or handling.
Maybe I misunderstood that sentence caused by my bad English. I knew
that in case the switch value is not present in any case inside the
switch, the program continues without doing anything (in the switch) and
without any problem.
int x = 3;
switch(x) {
case 1: printf("Hello");break;
case 2: printf("World");break;
}
Will the program execution continue without any defined behaviour?
[1]
https://clang.llvm.org/extra/clang-tidy/checks/bugprone/switch-missing-default-case.html
[toc] | [next] | [standalone]
| From | Janis Papanagnou <janis_papanagnou+ng@hotmail.com> |
|---|---|
| Date | 2025-10-22 11:32 +0200 |
| Message-ID | <10da8bo$hker$1@dont-email.me> |
| In reply to | #394647 |
On 22.10.2025 10:56, pozz wrote:
>
>> Switch statements without a default case can lead to unexpected
>> behavior and incomplete handling of all possible cases. When a switch
>> statement lacks a default case, if a value is encountered that does
>> not match any of the specified cases, the program will continue
>> execution without any defined behavior or handling.
>
> Maybe I misunderstood that sentence caused by my bad English. I knew
> that in case the switch value is not present in any case inside the
> switch, the program continues without doing anything (in the switch) and
> without any problem.
>
> int x = 3;
> switch(x) {
> case 1: printf("Hello");break;
> case 2: printf("World");break;
> }
>
> Will the program execution continue without any defined behaviour?
Your program fragment is well defined.
What the poster certainly tried to express was that in case you
haven't implemented a complete list of all possible cases and
also not provided a 'default' to catch all non-specified cases,
then you might get in troubles with your program, probably by
possible oversights, future extensions, new data, and whatnot.
Personally I have the habit to always define a default branch,
and even if that default is impossible to reach you'll find an
error message (like "internal error with unexpected value...")
generated at that place.
Janis
[toc] | [prev] | [next] | [standalone]
| From | Richard Harnden <richard.nospam@gmail.invalid> |
|---|---|
| Date | 2025-10-22 12:44 +0100 |
| Message-ID | <10dag3d$jkju$1@dont-email.me> |
| In reply to | #394648 |
On 22/10/2025 10:32, Janis Papanagnou wrote:
> On 22.10.2025 10:56, pozz wrote:
>>
>>> Switch statements without a default case can lead to unexpected
>>> behavior and incomplete handling of all possible cases. When a switch
>>> statement lacks a default case, if a value is encountered that does
>>> not match any of the specified cases, the program will continue
>>> execution without any defined behavior or handling.
>>
>> Maybe I misunderstood that sentence caused by my bad English. I knew
>> that in case the switch value is not present in any case inside the
>> switch, the program continues without doing anything (in the switch) and
>> without any problem.
>>
>> int x = 3;
>> switch(x) {
>> case 1: printf("Hello");break;
>> case 2: printf("World");break;
>> }
>>
>> Will the program execution continue without any defined behaviour?
>
> Your program fragment is well defined.
>
> What the poster certainly tried to express was that in case you
> haven't implemented a complete list of all possible cases and
> also not provided a 'default' to catch all non-specified cases,
> then you might get in troubles with your program, probably by
> possible oversights, future extensions, new data, and whatnot.
>
> Personally I have the habit to always define a default branch,
> and even if that default is impossible to reach you'll find an
> error message (like "internal error with unexpected value...")
> generated at that place.
>
Use an enum, and the compiler will warn you ...
$ cat x.c
#include <stdio.h>
enum x {A, B, C};
int main(void)
{
enum x x = C;
switch (x)
{
case A:
printf("A\n");
break;
case B:
printf("B\n");
break;
}
return 0;
}
$ gcc -Wall x.c
x.c: In function ‘main’:
x.c:9:9: warning: enumeration value ‘C’ not handled in switch [-Wswitch]
9 | switch (x)
| ^~~~~~
[toc] | [prev] | [next] | [standalone]
| From | Thiago Adams <thiago.adams@gmail.com> |
|---|---|
| Date | 2025-10-22 10:05 -0300 |
| Message-ID | <10dakrm$l3n2$1@dont-email.me> |
| In reply to | #394649 |
On 10/22/2025 8:44 AM, Richard Harnden wrote:
....
>> Your program fragment is well defined.
>>
>> What the poster certainly tried to express was that in case you
>> haven't implemented a complete list of all possible cases and
>> also not provided a 'default' to catch all non-specified cases,
>> then you might get in troubles with your program, probably by
>> possible oversights, future extensions, new data, and whatnot.
>>
>> Personally I have the habit to always define a default branch,
>> and even if that default is impossible to reach you'll find an
>> error message (like "internal error with unexpected value...")
>> generated at that place.
>>
> Use an enum, and the compiler will warn you ...
>
> $ cat x.c
> #include <stdio.h>
>
> enum x {A, B, C};
>
> int main(void)
> {
> enum x x = C;
>
> switch (x)
> {
> case A:
> printf("A\n");
> break;
>
> case B:
> printf("B\n");
> break;
> }
>
> return 0;
> }
>
> $ gcc -Wall x.c
> x.c: In function ‘main’:
> x.c:9:9: warning: enumeration value ‘C’ not handled in switch [-Wswitch]
> 9 | switch (x)
> | ^~~~~~
>
>
The problem with this GCC approach is when there are many enumerators
but only a few are used.
For instance :
enum E {A, B, C, /*..., Z*/};
1)
void f(enum E e)
{
switch (e)
{
//used
case A:
case B:
break;
//NON USED
case C:
...
case Z:
break;
};
}
The problem with (1) is when we have too many
non used enumerators, it is impractical to have a lot of switch cases.
One alternative is to use default for all the non used:
2)
void f(enum E e)
{
switch (e)
{
//used
case A:
case B:
break;
//NON USED (all others)
default:
break;
};
}
The problem with (2) is when we add a new enumerator and
this new enumerations should be used, but there is no warning and it
goes accidentally for default.
Solution?
In C2Y the new keyword _Countof was introduced.
It works returns the number of elements of array. IT IS FOR ARRAY ONLY.
I did an EXTENSION in my compiler where _Countof(enum E) also returns
the number of enumerators.
enum E2 {A, B};
static_assert(_Countof(enum E2) == 2);
(It also could be a new keyword.
static_assert(_EnumCount(enum E2) == 2);)
Having this we can do:
3)
void f(enum E e)
{
switch (e)
{
//used
case A:
case B:
break;
default:
static_assert(_EnumCount(enum E2) == 20);
break;
};
}
Then when adding a new enumerator the programmer will have to review
this code and update to 21 if it is not used, or handle it in a new case.
This is also useful in other scenarios. For instance:
enum E parse_enum_e(const char* s)
{
if (strcmp(s, "A") == 0) return A;
if (strcmp(s, "B") == 0) return B;
if (strcmp(s, "C") == 0) return C;
if (strcmp(s, "D") == 0) return D;
if (strcmp(s, "E") == 0) return E;
if (strcmp(s, "F") == 0) return F;
static_assert(_Countof(enum E) == 6);
return A;
}
If a new enumerator is added we need to include it.
[toc] | [prev] | [next] | [standalone]
| From | Kaz Kylheku <643-408-1753@kylheku.com> |
|---|---|
| Date | 2025-10-22 18:22 +0000 |
| Message-ID | <20251022111414.630@kylheku.com> |
| In reply to | #394651 |
On 2025-10-22, Thiago Adams <thiago.adams@gmail.com> wrote:
> On 10/22/2025 8:44 AM, Richard Harnden wrote:
> ....
>>> Your program fragment is well defined.
>>>
>>> What the poster certainly tried to express was that in case you
>>> haven't implemented a complete list of all possible cases and
>>> also not provided a 'default' to catch all non-specified cases,
>>> then you might get in troubles with your program, probably by
>>> possible oversights, future extensions, new data, and whatnot.
>>>
>>> Personally I have the habit to always define a default branch,
>>> and even if that default is impossible to reach you'll find an
>>> error message (like "internal error with unexpected value...")
>>> generated at that place.
>>>
>> Use an enum, and the compiler will warn you ...
>>
>> $ cat x.c
>> #include <stdio.h>
>>
>> enum x {A, B, C};
>>
>> int main(void)
>> {
>> enum x x = C;
>>
>> switch (x)
>> {
>> case A:
>> printf("A\n");
>> break;
>>
>> case B:
>> printf("B\n");
>> break;
>> }
>>
>> return 0;
>> }
>>
>> $ gcc -Wall x.c
>> x.c: In function ‘main’:
>> x.c:9:9: warning: enumeration value ‘C’ not handled in switch [-Wswitch]
>> 9 | switch (x)
>> | ^~~~~~
>>
>>
>
> The problem with this GCC approach is when there are many enumerators
> but only a few are used.
The problem with the C and GCC approach is that there is no
one-size-fits all solution.
Some switches are intended to be exhaustive, such that
missing a case is a bug.
Some are not.
You need an "eswitch" for the exhaustively handled enumerations, and
switch for the others.
GCC can turn on diagnostics over ranges of a file with pragma
and there is also _Pragram, but it's all too clumsy.
In Common Lisp we have case vs ecase:
[1]> (case 3 (0 "zero") (1 "one") (2 "two"))
NIL
[2]> (case 0 (0 "zero") (1 "one") (2 "two"))
"zero"
[3]> (ecase 3 (0 "zero") (1 "one") (2 "two"))
*** - The value of 3 must be one of 0, 1, 2
The value is: 3
If falling through without hitting a case would be wrong, you use ecase;
problem solved.
--
TXR Programming Language: http://nongnu.org/txr
Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
Mastodon: @Kazinator@mstdn.ca
[toc] | [prev] | [next] | [standalone]
| From | David Brown <david.brown@hesbynett.no> |
|---|---|
| Date | 2025-10-23 09:12 +0200 |
| Message-ID | <10dckhj$1dnn6$2@dont-email.me> |
| In reply to | #394661 |
On 22/10/2025 20:22, Kaz Kylheku wrote:
> On 2025-10-22, Thiago Adams <thiago.adams@gmail.com> wrote:
>> On 10/22/2025 8:44 AM, Richard Harnden wrote:
>> ....
>>>> Your program fragment is well defined.
>>>>
>>>> What the poster certainly tried to express was that in case you
>>>> haven't implemented a complete list of all possible cases and
>>>> also not provided a 'default' to catch all non-specified cases,
>>>> then you might get in troubles with your program, probably by
>>>> possible oversights, future extensions, new data, and whatnot.
>>>>
>>>> Personally I have the habit to always define a default branch,
>>>> and even if that default is impossible to reach you'll find an
>>>> error message (like "internal error with unexpected value...")
>>>> generated at that place.
>>>>
>>> Use an enum, and the compiler will warn you ...
>>>
>>> $ cat x.c
>>> #include <stdio.h>
>>>
>>> enum x {A, B, C};
>>>
>>> int main(void)
>>> {
>>> enum x x = C;
>>>
>>> switch (x)
>>> {
>>> case A:
>>> printf("A\n");
>>> break;
>>>
>>> case B:
>>> printf("B\n");
>>> break;
>>> }
>>>
>>> return 0;
>>> }
>>>
>>> $ gcc -Wall x.c
>>> x.c: In function ‘main’:
>>> x.c:9:9: warning: enumeration value ‘C’ not handled in switch [-Wswitch]
>>> 9 | switch (x)
>>> | ^~~~~~
>>>
>>>
>>
>> The problem with this GCC approach is when there are many enumerators
>> but only a few are used.
>
> The problem with the C and GCC approach is that there is no
> one-size-fits all solution.
>
> Some switches are intended to be exhaustive, such that
> missing a case is a bug.
>
> Some are not.
>
> You need an "eswitch" for the exhaustively handled enumerations, and
> switch for the others.
>
> GCC can turn on diagnostics over ranges of a file with pragma
> and there is also _Pragram, but it's all too clumsy.
>
The gcc approach works fine in almost all situations - use
"-Wswitch=error", and add a default case if your switch is not meant to
handle all enumeration values. If the default should do nothing, it's
just "default: // Not all cases need handling". If the default should
never happen, "default: __builtin_unreachable();" or "default:
__builtin_trap();" might be appropriate.
[toc] | [prev] | [next] | [standalone]
| From | Thiago Adams <thiago.adams@gmail.com> |
|---|---|
| Date | 2025-10-23 08:03 -0300 |
| Message-ID | <10dd215$3totj$2@dont-email.me> |
| In reply to | #394674 |
On 10/23/2025 4:12 AM, David Brown wrote:
> On 22/10/2025 20:22, Kaz Kylheku wrote:
>> On 2025-10-22, Thiago Adams <thiago.adams@gmail.com> wrote:
>>> On 10/22/2025 8:44 AM, Richard Harnden wrote:
>>> ....
>>>>> Your program fragment is well defined.
>>>>>
>>>>> What the poster certainly tried to express was that in case you
>>>>> haven't implemented a complete list of all possible cases and
>>>>> also not provided a 'default' to catch all non-specified cases,
>>>>> then you might get in troubles with your program, probably by
>>>>> possible oversights, future extensions, new data, and whatnot.
>>>>>
>>>>> Personally I have the habit to always define a default branch,
>>>>> and even if that default is impossible to reach you'll find an
>>>>> error message (like "internal error with unexpected value...")
>>>>> generated at that place.
>>>>>
>>>> Use an enum, and the compiler will warn you ...
>>>>
>>>> $ cat x.c
>>>> #include <stdio.h>
>>>>
>>>> enum x {A, B, C};
>>>>
>>>> int main(void)
>>>> {
>>>> enum x x = C;
>>>>
>>>> switch (x)
>>>> {
>>>> case A:
>>>> printf("A\n");
>>>> break;
>>>>
>>>> case B:
>>>> printf("B\n");
>>>> break;
>>>> }
>>>>
>>>> return 0;
>>>> }
>>>>
>>>> $ gcc -Wall x.c
>>>> x.c: In function ‘main’:
>>>> x.c:9:9: warning: enumeration value ‘C’ not handled in switch [-
>>>> Wswitch]
>>>> 9 | switch (x)
>>>> | ^~~~~~
>>>>
>>>>
>>>
>>> The problem with this GCC approach is when there are many enumerators
>>> but only a few are used.
>>
>> The problem with the C and GCC approach is that there is no
>> one-size-fits all solution.
>>
>> Some switches are intended to be exhaustive, such that
>> missing a case is a bug.
>>
>> Some are not.
>>
>> You need an "eswitch" for the exhaustively handled enumerations, and
>> switch for the others.
>>
>> GCC can turn on diagnostics over ranges of a file with pragma
>> and there is also _Pragram, but it's all too clumsy.
>>
>
> The gcc approach works fine in almost all situations - use "-
> Wswitch=error", and add a default case if your switch is not meant to
> handle all enumeration values. If the default should do nothing, it's
> just "default: // Not all cases need handling". If the default should
> never happen, "default: __builtin_unreachable();" or "default:
> __builtin_trap();" might be appropriate.
>
>
>
But then instead a compiler time error (like I suggest) you leave it for
runtime.
[toc] | [prev] | [next] | [standalone]
| From | David Brown <david.brown@hesbynett.no> |
|---|---|
| Date | 2025-10-23 17:06 +0200 |
| Message-ID | <10ddg8q$1r5el$1@dont-email.me> |
| In reply to | #394679 |
On 23/10/2025 13:03, Thiago Adams wrote:
> On 10/23/2025 4:12 AM, David Brown wrote:
>> On 22/10/2025 20:22, Kaz Kylheku wrote:
>>> On 2025-10-22, Thiago Adams <thiago.adams@gmail.com> wrote:
>>>> On 10/22/2025 8:44 AM, Richard Harnden wrote:
>>>> ....
>>>>>> Your program fragment is well defined.
>>>>>>
>>>>>> What the poster certainly tried to express was that in case you
>>>>>> haven't implemented a complete list of all possible cases and
>>>>>> also not provided a 'default' to catch all non-specified cases,
>>>>>> then you might get in troubles with your program, probably by
>>>>>> possible oversights, future extensions, new data, and whatnot.
>>>>>>
>>>>>> Personally I have the habit to always define a default branch,
>>>>>> and even if that default is impossible to reach you'll find an
>>>>>> error message (like "internal error with unexpected value...")
>>>>>> generated at that place.
>>>>>>
>>>>> Use an enum, and the compiler will warn you ...
>>>>>
>>>>> $ cat x.c
>>>>> #include <stdio.h>
>>>>>
>>>>> enum x {A, B, C};
>>>>>
>>>>> int main(void)
>>>>> {
>>>>> enum x x = C;
>>>>>
>>>>> switch (x)
>>>>> {
>>>>> case A:
>>>>> printf("A\n");
>>>>> break;
>>>>>
>>>>> case B:
>>>>> printf("B\n");
>>>>> break;
>>>>> }
>>>>>
>>>>> return 0;
>>>>> }
>>>>>
>>>>> $ gcc -Wall x.c
>>>>> x.c: In function ‘main’:
>>>>> x.c:9:9: warning: enumeration value ‘C’ not handled in switch [-
>>>>> Wswitch]
>>>>> 9 | switch (x)
>>>>> | ^~~~~~
>>>>>
>>>>>
>>>>
>>>> The problem with this GCC approach is when there are many enumerators
>>>> but only a few are used.
>>>
>>> The problem with the C and GCC approach is that there is no
>>> one-size-fits all solution.
>>>
>>> Some switches are intended to be exhaustive, such that
>>> missing a case is a bug.
>>>
>>> Some are not.
>>>
>>> You need an "eswitch" for the exhaustively handled enumerations, and
>>> switch for the others.
>>>
>>> GCC can turn on diagnostics over ranges of a file with pragma
>>> and there is also _Pragram, but it's all too clumsy.
>>>
>>
>> The gcc approach works fine in almost all situations - use "-
>> Wswitch=error", and add a default case if your switch is not meant to
>> handle all enumeration values. If the default should do nothing, it's
>> just "default: // Not all cases need handling". If the default should
>> never happen, "default: __builtin_unreachable();" or "default:
>> __builtin_trap();" might be appropriate.
>>
>>
>>
>
> But then instead a compiler time error (like I suggest) you leave it for
> runtime.
>
>
As I said - use "-Wswitch=error". That gives you a compile-time error -
not merely a warning, as you had suggested. But /if/ your switch is not
meant to handle all cases, which was what Kaz was complaining about,
then you add a default case. I agree with you that a compile-time error
is best when possible, which is why that was my suggestion.
[toc] | [prev] | [next] | [standalone]
| From | Thiago Adams <thiago.adams@gmail.com> |
|---|---|
| Date | 2025-10-23 13:15 -0300 |
| Message-ID | <10ddkb2$1tadp$1@dont-email.me> |
| In reply to | #394681 |
On 10/23/2025 12:06 PM, David Brown wrote:
> On 23/10/2025 13:03, Thiago Adams wrote:
>> On 10/23/2025 4:12 AM, David Brown wrote:
>>> On 22/10/2025 20:22, Kaz Kylheku wrote:
>>>> On 2025-10-22, Thiago Adams <thiago.adams@gmail.com> wrote:
>>>>> On 10/22/2025 8:44 AM, Richard Harnden wrote:
>>>>> ....
>>>>>>> Your program fragment is well defined.
>>>>>>>
>>>>>>> What the poster certainly tried to express was that in case you
>>>>>>> haven't implemented a complete list of all possible cases and
>>>>>>> also not provided a 'default' to catch all non-specified cases,
>>>>>>> then you might get in troubles with your program, probably by
>>>>>>> possible oversights, future extensions, new data, and whatnot.
>>>>>>>
>>>>>>> Personally I have the habit to always define a default branch,
>>>>>>> and even if that default is impossible to reach you'll find an
>>>>>>> error message (like "internal error with unexpected value...")
>>>>>>> generated at that place.
>>>>>>>
>>>>>> Use an enum, and the compiler will warn you ...
>>>>>>
>>>>>> $ cat x.c
>>>>>> #include <stdio.h>
>>>>>>
>>>>>> enum x {A, B, C};
>>>>>>
>>>>>> int main(void)
>>>>>> {
>>>>>> enum x x = C;
>>>>>>
>>>>>> switch (x)
>>>>>> {
>>>>>> case A:
>>>>>> printf("A\n");
>>>>>> break;
>>>>>>
>>>>>> case B:
>>>>>> printf("B\n");
>>>>>> break;
>>>>>> }
>>>>>>
>>>>>> return 0;
>>>>>> }
>>>>>>
>>>>>> $ gcc -Wall x.c
>>>>>> x.c: In function ‘main’:
>>>>>> x.c:9:9: warning: enumeration value ‘C’ not handled in switch [-
>>>>>> Wswitch]
>>>>>> 9 | switch (x)
>>>>>> | ^~~~~~
>>>>>>
>>>>>>
>>>>>
>>>>> The problem with this GCC approach is when there are many enumerators
>>>>> but only a few are used.
>>>>
>>>> The problem with the C and GCC approach is that there is no
>>>> one-size-fits all solution.
>>>>
>>>> Some switches are intended to be exhaustive, such that
>>>> missing a case is a bug.
>>>>
>>>> Some are not.
>>>>
>>>> You need an "eswitch" for the exhaustively handled enumerations, and
>>>> switch for the others.
>>>>
>>>> GCC can turn on diagnostics over ranges of a file with pragma
>>>> and there is also _Pragram, but it's all too clumsy.
>>>>
>>>
>>> The gcc approach works fine in almost all situations - use "-
>>> Wswitch=error", and add a default case if your switch is not meant to
>>> handle all enumeration values. If the default should do nothing,
>>> it's just "default: // Not all cases need handling". If the default
>>> should never happen, "default: __builtin_unreachable();" or "default:
>>> __builtin_trap();" might be appropriate.
>>>
>>>
>>>
>>
>> But then instead a compiler time error (like I suggest) you leave it
>> for runtime.
>>
>>
>
> As I said - use "-Wswitch=error". That gives you a compile-time error -
> not merely a warning, as you had suggested. But /if/ your switch is not
> meant to handle all cases, which was what Kaz was complaining about,
> then you add a default case. I agree with you that a compile-time error
> is best when possible, which is why that was my suggestion.
>
>
>
I think my sample is not covered by any GCC flag
I will copy past it again:
--------
The problem with this GCC approach is when there are many enumerators
but only a few are used.
For instance :
enum E {A, B, C /*, ...*/, Z};
1)
void f(enum E e)
{
switch (e)
{
//used
case A:
case B:
break;
//NON USED
case C:
...
case Z:
break;
};
}
The problem with (1) is when we have too many
non used enumerators, it is impractical to have a lot of switch cases.
One alternative is to use default for all the non used:
2)
void f(enum E e)
{
switch (e)
{
//used
case A:
case B:
break;
//NON USED (all others)
default:
break;
};
}
The problem with (2) is when we add a new enumerator and
this new enumerations should be used, but there is no warning and it
goes accidentally for default.
Solution?
In C2Y the new keyword _Countof was introduced.
It works returns the number of elements of array. IT IS FOR ARRAY ONLY.
I did an EXTENSION in my compiler where _Countof(enum E) also returns
the number of enumerators.
enum E2 {A, B};
static_assert(_Countof(enum E2) == 2);
(It also could be a new keyword.
static_assert(_EnumCount(enum E2) == 2);)
Having this we can do:
3)
void f(enum E e)
{
switch (e)
{
//used
case A:
case B:
break;
default:
static_assert(_EnumCount(enum E2) == 20);
break;
};
}
Then when adding a new enumerator the programmer will have to review
this code and update to 21 if it is not used, or handle it in a new case.
This is also useful in other scenarios. For instance:
enum E parse_enum_e(const char* s)
{
if (strcmp(s, "A") == 0) return A;
if (strcmp(s, "B") == 0) return B;
if (strcmp(s, "C") == 0) return C;
if (strcmp(s, "D") == 0) return D;
if (strcmp(s, "E") == 0) return E;
if (strcmp(s, "F") == 0) return F;
static_assert(_Countof(enum E) == 6);
return A;
}
If a new enumerator is added we need to include it.
--------
[toc] | [prev] | [next] | [standalone]
| From | bart <bc@freeuk.com> |
|---|---|
| Date | 2025-10-23 17:46 +0100 |
| Message-ID | <10ddm5o$1su1m$1@dont-email.me> |
| In reply to | #394682 |
On 23/10/2025 17:15, Thiago Adams wrote:
> On 10/23/2025 12:06 PM, David Brown wrote:
> One alternative is to use default for all the non used:
>
> 2)
> void f(enum E e)
> {
> switch (e)
> {
> //used
> case A:
> case B:
> break;
>
> //NON USED (all others)
> default:
> break;
> };
> }
>
>
> The problem with (2) is when we add a new enumerator and
> this new enumerations should be used,
How does the compiler know whether it should be used or not?
And if not, how do you stop the warning? (Of non-exhaustive checking, if
there is one.)
> In C2Y the new keyword _Countof was introduced.
> It works returns the number of elements of array. IT IS FOR ARRAY ONLY.
>
> I did an EXTENSION in my compiler where _Countof(enum E) also returns
> the number of enumerators.
>
>
> enum E2 {A, B};
> static_assert(_Countof(enum E2) == 2);
>
> (It also could be a new keyword.
> static_assert(_EnumCount(enum E2) == 2);)
>
> Having this we can do:
>
> 3)
> void f(enum E e)
> {
> switch (e)
> {
> //used
> case A:
> case B:
> break;
>
> default:
> static_assert(_EnumCount(enum E2) == 20);
> break;
> };
>
> }
>
> Then when adding a new enumerator the programmer will have to review
> this code and update to 21 if it is not used, or handle it in a new case.
It sounds limited. What if part of the set of enums is conditional? Then
that '20' can vary depending on some macro value.
But having a hard-coded 20 is also problematical; do you have to
painstakingly count maybe 200 enumerations? And then keep it maintained?
What if you put in the wrong number?
What if you want to temporarily comment out some of those cases, or some
of the enums?
There may also be muliple 'switch' statements working on the same set of
enums, which may need to check a different subset.
Anyway, I don't see how this helps with reporting whether a enum that
should be in a 'case' label is missing, or vice versa, when it should be
checked.
>
> This is also useful in other scenarios. For instance:
>
>
> enum E parse_enum_e(const char* s)
> {
> if (strcmp(s, "A") == 0) return A;
> if (strcmp(s, "B") == 0) return B;
> if (strcmp(s, "C") == 0) return C;
> if (strcmp(s, "D") == 0) return D;
> if (strcmp(s, "E") == 0) return E;
> if (strcmp(s, "F") == 0) return F;
> static_assert(_Countof(enum E) == 6);
>
> return A;
> }
>
> If a new enumerator is added we need to include it.
Another hard-coded value! An anti-pattern I think. Most of what I said
above applies here. You have N enum values, you have N checking lines,
and you have that static assert on N. But what happens if you leave out
a checking line, or cidentally have the D line twice then check F?
What if you forget to update it to 7, or write it as 5 anyway?
It looks like a weak check that also adds more opportunities for error.
I think getting the number of values of an enum type has some uses: you
can use to iterate over the values, when they start from zero and are
consecutive.
Or it can iterate over arrays indexed by the enum. And it can be used to
set the bounds of such arrays. These sound more useful and more reliable
than your asserts!
[toc] | [prev] | [next] | [standalone]
| From | Thiago Adams <thiago.adams@gmail.com> |
|---|---|
| Date | 2025-10-23 14:12 -0300 |
| Message-ID | <10ddnlv$1uu0p$1@dont-email.me> |
| In reply to | #394683 |
On 10/23/2025 1:46 PM, bart wrote:
> On 23/10/2025 17:15, Thiago Adams wrote:
>> On 10/23/2025 12:06 PM, David Brown wrote:
>
>> One alternative is to use default for all the non used:
>>
>> 2)
>> void f(enum E e)
>> {
>> switch (e)
>> {
>> //used
>> case A:
>> case B:
>> break;
>>
>> //NON USED (all others)
>> default:
>> break;
>> };
>> }
>>
>>
>> The problem with (2) is when we add a new enumerator and
>> this new enumerations should be used,
>
> How does the compiler know whether it should be used or not?
>
> And if not, how do you stop the warning? (Of non-exhaustive checking, if
> there is one.)
>
>> In C2Y the new keyword _Countof was introduced.
>> It works returns the number of elements of array. IT IS FOR ARRAY ONLY.
>>
>> I did an EXTENSION in my compiler where _Countof(enum E) also returns
>> the number of enumerators.
>>
>>
>> enum E2 {A, B};
>> static_assert(_Countof(enum E2) == 2);
>>
>> (It also could be a new keyword.
>> static_assert(_EnumCount(enum E2) == 2);)
>>
>> Having this we can do:
>>
>> 3)
>> void f(enum E e)
>> {
>> switch (e)
>> {
>> //used
>> case A:
>> case B:
>> break;
>>
>> default:
>> static_assert(_EnumCount(enum E2) == 20);
>> break;
>> };
>>
>> }
>>
>> Then when adding a new enumerator the programmer will have to review
>> this code and update to 21 if it is not used, or handle it in a new case.
>
> It sounds limited. What if part of the set of enums is conditional? Then
> that '20' can vary depending on some macro value.
>
> But having a hard-coded 20 is also problematical; do you have to
> painstakingly count maybe 200 enumerations? And then keep it maintained?
> What if you put in the wrong number?
>
> What if you want to temporarily comment out some of those cases, or some
> of the enums?
>
> There may also be muliple 'switch' statements working on the same set of
> enums, which may need to check a different subset.
>
> Anyway, I don't see how this helps with reporting whether a enum that
> should be in a 'case' label is missing, or vice versa, when it should be
> checked.
>
>
>>
>> This is also useful in other scenarios. For instance:
>>
>>
>> enum E parse_enum_e(const char* s)
>> {
>> if (strcmp(s, "A") == 0) return A;
>> if (strcmp(s, "B") == 0) return B;
>> if (strcmp(s, "C") == 0) return C;
>> if (strcmp(s, "D") == 0) return D;
>> if (strcmp(s, "E") == 0) return E;
>> if (strcmp(s, "F") == 0) return F;
>> static_assert(_Countof(enum E) == 6);
>>
>> return A;
>> }
>>
>> If a new enumerator is added we need to include it.
>
> Another hard-coded value! An anti-pattern I think. Most of what I said
> above applies here. You have N enum values, you have N checking lines,
> and you have that static assert on N. But what happens if you leave out
> a checking line, or cidentally have the D line twice then check F?
>
> What if you forget to update it to 7, or write it as 5 anyway?
>
> It looks like a weak check that also adds more opportunities for error.
>
> I think getting the number of values of an enum type has some uses: you
> can use to iterate over the values, when they start from zero and are
> consecutive.
>
> Or it can iterate over arrays indexed by the enum. And it can be used to
> set the bounds of such arrays. These sound more useful and more reliable
> than your asserts!
I am trying to avoid having to answer each question (but if necessary I
can do that)
Consider:
bool is_free(enum product product)
{
switch (procuct)
{
case combo1:
case combo2:
return true;
default:
break;
}
static_assert(_countof(enum product) == 100);
return false;
}
I have 100 products (or more), just few are free.
I have this function that return true is the product is free.
If I add a new product I need update this function. The new product can
be free or not. default: assert(false) only catches the problem in runtime.
How to count? You can print _countof(enum product)
[toc] | [prev] | [next] | [standalone]
| From | David Brown <david.brown@hesbynett.no> |
|---|---|
| Date | 2025-10-23 22:40 +0200 |
| Message-ID | <10de3sh$24ftv$1@dont-email.me> |
| In reply to | #394682 |
On 23/10/2025 18:15, Thiago Adams wrote:
> On 10/23/2025 12:06 PM, David Brown wrote:
>> On 23/10/2025 13:03, Thiago Adams wrote:
>>> On 10/23/2025 4:12 AM, David Brown wrote:
>>>> On 22/10/2025 20:22, Kaz Kylheku wrote:
>>>>> On 2025-10-22, Thiago Adams <thiago.adams@gmail.com> wrote:
>>>>>> On 10/22/2025 8:44 AM, Richard Harnden wrote:
>>>>>> ....
>>>>>>>> Your program fragment is well defined.
>>>>>>>>
>>>>>>>> What the poster certainly tried to express was that in case you
>>>>>>>> haven't implemented a complete list of all possible cases and
>>>>>>>> also not provided a 'default' to catch all non-specified cases,
>>>>>>>> then you might get in troubles with your program, probably by
>>>>>>>> possible oversights, future extensions, new data, and whatnot.
>>>>>>>>
>>>>>>>> Personally I have the habit to always define a default branch,
>>>>>>>> and even if that default is impossible to reach you'll find an
>>>>>>>> error message (like "internal error with unexpected value...")
>>>>>>>> generated at that place.
>>>>>>>>
>>>>>>> Use an enum, and the compiler will warn you ...
>>>>>>>
>>>>>>> $ cat x.c
>>>>>>> #include <stdio.h>
>>>>>>>
>>>>>>> enum x {A, B, C};
>>>>>>>
>>>>>>> int main(void)
>>>>>>> {
>>>>>>> enum x x = C;
>>>>>>>
>>>>>>> switch (x)
>>>>>>> {
>>>>>>> case A:
>>>>>>> printf("A\n");
>>>>>>> break;
>>>>>>>
>>>>>>> case B:
>>>>>>> printf("B\n");
>>>>>>> break;
>>>>>>> }
>>>>>>>
>>>>>>> return 0;
>>>>>>> }
>>>>>>>
>>>>>>> $ gcc -Wall x.c
>>>>>>> x.c: In function ‘main’:
>>>>>>> x.c:9:9: warning: enumeration value ‘C’ not handled in switch [-
>>>>>>> Wswitch]
>>>>>>> 9 | switch (x)
>>>>>>> | ^~~~~~
>>>>>>>
>>>>>>>
>>>>>>
>>>>>> The problem with this GCC approach is when there are many enumerators
>>>>>> but only a few are used.
>>>>>
>>>>> The problem with the C and GCC approach is that there is no
>>>>> one-size-fits all solution.
>>>>>
>>>>> Some switches are intended to be exhaustive, such that
>>>>> missing a case is a bug.
>>>>>
>>>>> Some are not.
>>>>>
>>>>> You need an "eswitch" for the exhaustively handled enumerations, and
>>>>> switch for the others.
>>>>>
>>>>> GCC can turn on diagnostics over ranges of a file with pragma
>>>>> and there is also _Pragram, but it's all too clumsy.
>>>>>
>>>>
>>>> The gcc approach works fine in almost all situations - use "-
>>>> Wswitch=error", and add a default case if your switch is not meant
>>>> to handle all enumeration values. If the default should do nothing,
>>>> it's just "default: // Not all cases need handling". If the default
>>>> should never happen, "default: __builtin_unreachable();" or
>>>> "default: __builtin_trap();" might be appropriate.
>>>>
>>>>
>>>>
>>>
>>> But then instead a compiler time error (like I suggest) you leave it
>>> for runtime.
>>>
>>>
>>
>> As I said - use "-Wswitch=error". That gives you a compile-time error
>> - not merely a warning, as you had suggested. But /if/ your switch is
>> not meant to handle all cases, which was what Kaz was complaining
>> about, then you add a default case. I agree with you that a compile-
>> time error is best when possible, which is why that was my suggestion.
>>
>>
>>
>
>
> I think my sample is not covered by any GCC flag
>
>
> I will copy past it again:
> --------
> The problem with this GCC approach is when there are many enumerators
> but only a few are used.
>
> For instance :
>
> enum E {A, B, C /*, ...*/, Z};
>
> 1)
> void f(enum E e)
> {
> switch (e)
> {
> //used
> case A:
> case B:
> break;
>
> //NON USED
> case C:
> ...
> case Z:
> break;
> };
> }
>
> The problem with (1) is when we have too many
> non used enumerators, it is impractical to have a lot of switch cases.
>
> One alternative is to use default for all the non used:
>
> 2)
> void f(enum E e)
> {
> switch (e)
> {
> //used
> case A:
> case B:
> break;
>
> //NON USED (all others)
> default:
> break;
> };
> }
>
>
> The problem with (2) is when we add a new enumerator and
> this new enumerations should be used, but there is no warning and it
> goes accidentally for default.
>
>
> Solution?
>
> In C2Y the new keyword _Countof was introduced.
> It works returns the number of elements of array. IT IS FOR ARRAY ONLY.
>
> I did an EXTENSION in my compiler where _Countof(enum E) also returns
> the number of enumerators.
>
>
> enum E2 {A, B};
> static_assert(_Countof(enum E2) == 2);
>
> (It also could be a new keyword.
> static_assert(_EnumCount(enum E2) == 2);)
>
> Having this we can do:
>
> 3)
> void f(enum E e)
> {
> switch (e)
> {
> //used
> case A:
> case B:
> break;
>
> default:
> static_assert(_EnumCount(enum E2) == 20);
> break;
> };
>
> }
>
I appreciate that enumerations in C are missing a lot of potential
compared to enumerations in some other languages (they could be real new
types that do not implicitly convert with "int", they could have a
"count" feature, they could have a feature for having the identifier
names in a const array of strings, etc.). But none of this is at all
hard with today's enums :
enum E1 { A, B, C, D, last_E1 = D };
void f(enum E1 e)
{
switch (e)
{
//used
case A:
case B:
break;
default:
static_assert(last_E1 == D);
// or, according to preference
static_assert(last_E1 == 4);
break;
};
}
People do this regularly. Personally, I usually prefer to have the
"last", "max", or "count" indicator outside the enumeration :
enum E1 { A, B, C, D };
static const int count_of_E1 = D;
It is not standardised, so different people use different names, but
it's perfectly doable.
And if you want to automate it a little more and negate the possibility
of forgetting to change the "count_of_E1" line, because automation of
these things is nice, then I think you could easily put together an
XMacro style solution.
In a new, different language, I'd want better enumerations with more
features, and this kind of thing could be handled better in the
language. But for C, I can't see this being worth the effort for an
extension.
[toc] | [prev] | [next] | [standalone]
| From | scott@slp53.sl.home (Scott Lurndal) |
|---|---|
| Date | 2025-10-23 20:51 +0000 |
| Message-ID | <5zwKQ.658591$7Ika.341536@fx17.iad> |
| In reply to | #394687 |
David Brown <david.brown@hesbynett.no> writes:
>On 23/10/2025 18:15, Thiago Adams wrote:
>> On 10/23/2025 12:06 PM, David Brown wrote:
>People do this regularly. Personally, I usually prefer to have the
>"last", "max", or "count" indicator outside the enumeration :
>
> enum E1 { A, B, C, D };
> static const int count_of_E1 = D;
D has the value '3', yet there are four elements in the
set. Perhaps the name should be count_of_E1_minus_1?
How do you use count_of_E1 in this case?
Too bad 'sizeof' doesn't work on typed enums.
[toc] | [prev] | [next] | [standalone]
| From | Keith Thompson <Keith.S.Thompson+u@gmail.com> |
|---|---|
| Date | 2025-10-23 16:21 -0700 |
| Message-ID | <87bjlx2ohg.fsf@example.invalid> |
| In reply to | #394688 |
scott@slp53.sl.home (Scott Lurndal) writes:
> David Brown <david.brown@hesbynett.no> writes:
>>On 23/10/2025 18:15, Thiago Adams wrote:
>>> On 10/23/2025 12:06 PM, David Brown wrote:
>
>>People do this regularly. Personally, I usually prefer to have the
>>"last", "max", or "count" indicator outside the enumeration :
>>
>> enum E1 { A, B, C, D };
>> static const int count_of_E1 = D;
>
> D has the value '3', yet there are four elements in the
> set. Perhaps the name should be count_of_E1_minus_1?
>
> How do you use count_of_E1 in this case?
>
> Too bad 'sizeof' doesn't work on typed enums.
sizeof works just fine on typed enums. It just doesn't tell you how
many members it has.
--
Keith Thompson (The_Other_Keith) Keith.S.Thompson+u@gmail.com
void Void(void) { Void(); } /* The recursive call of the void */
[toc] | [prev] | [next] | [standalone]
| From | David Brown <david.brown@hesbynett.no> |
|---|---|
| Date | 2025-10-24 08:59 +0200 |
| Message-ID | <10df844$2dph4$1@dont-email.me> |
| In reply to | #394688 |
On 23/10/2025 22:51, Scott Lurndal wrote:
> David Brown <david.brown@hesbynett.no> writes:
>> On 23/10/2025 18:15, Thiago Adams wrote:
>>> On 10/23/2025 12:06 PM, David Brown wrote:
>
>> People do this regularly. Personally, I usually prefer to have the
>> "last", "max", or "count" indicator outside the enumeration :
>>
>> enum E1 { A, B, C, D };
>> static const int count_of_E1 = D;
>
> D has the value '3', yet there are four elements in the
> set. Perhaps the name should be count_of_E1_minus_1?
>
Argh! That's the danger of writing code directly in Usenet posts - it's
easy to make the silliest of mistakes. Clearly "D" is fine for "max" or
"last", but a "count" would want "D + 1".
> How do you use count_of_E1 in this case?
>
> Too bad 'sizeof' doesn't work on typed enums.
Sizeof does work on enumerated types :
enum E : unsigned short { a, b, c, d, e, f };
int x = sizeof(enum E);
int y = sizeof(a);
"x" and "y" are both 2 on x86. It would be a terrible mistake for it to
be anything else here.
A better possibility is the _Lengthof operator in the future C2y
standard. It is for arrays, giving the number of elements (rather than
the size). I believe it could be practical to have it apply to
enumeration types and give the count of the elements, though it is not
clear what it should do if the enumeration constants are explicitly
initialised in different ways.
(For those that don't obsessively read the latest standards, specifying
the underlying type of an enumeration is a C23 addition. But sizeof
works without that too.)
[toc] | [prev] | [next] | [standalone]
| From | scott@slp53.sl.home (Scott Lurndal) |
|---|---|
| Date | 2025-10-24 15:51 +0000 |
| Message-ID | <6gNKQ.166393$D_Tb.132753@fx47.iad> |
| In reply to | #394700 |
David Brown <david.brown@hesbynett.no> writes:
>On 23/10/2025 22:51, Scott Lurndal wrote:
>> David Brown <david.brown@hesbynett.no> writes:
>>> On 23/10/2025 18:15, Thiago Adams wrote:
>>>> On 10/23/2025 12:06 PM, David Brown wrote:
>>
>>> People do this regularly. Personally, I usually prefer to have the
>>> "last", "max", or "count" indicator outside the enumeration :
>>>
>>> enum E1 { A, B, C, D };
>>> static const int count_of_E1 = D;
>>
>> D has the value '3', yet there are four elements in the
>> set. Perhaps the name should be count_of_E1_minus_1?
>>
>
>Argh! That's the danger of writing code directly in Usenet posts - it's
>easy to make the silliest of mistakes. Clearly "D" is fine for "max" or
>"last", but a "count" would want "D + 1".
>
>> How do you use count_of_E1 in this case?
>>
>> Too bad 'sizeof' doesn't work on typed enums.
>
>Sizeof does work on enumerated types :
yes, I'm aware. I should have added 'to determine the number of enums'.
[toc] | [prev] | [next] | [standalone]
| From | tTh <tth@none.invalid> |
|---|---|
| Date | 2025-10-23 23:57 +0200 |
| Message-ID | <10de8bn$1hhn$1@news.gegeweb.eu> |
| In reply to | #394687 |
On 10/23/25 22:40, David Brown wrote:
> Personally, I usually prefer to have the "last", "max", or "count"
> indicator outside the enumeration :
>
> enum E1 { A, B, C, D };
> static const int count_of_E1 = D;
Why did you use 'static' here ?
--
** **
* tTh des Bourtoulots *
* http://maison.tth.netlib.re/ *
** **
[toc] | [prev] | [next] | [standalone]
| From | David Brown <david.brown@hesbynett.no> |
|---|---|
| Date | 2025-10-24 09:22 +0200 |
| Message-ID | <10df9fk$2dph4$2@dont-email.me> |
| In reply to | #394689 |
On 23/10/2025 23:57, tTh wrote:
> On 10/23/25 22:40, David Brown wrote:
>> Personally, I usually prefer to have the "last", "max", or "count"
>> indicator outside the enumeration :
>>
>> enum E1 { A, B, C, D };
>> static const int count_of_E1 = D;
>
> Why did you use 'static' here ?
>
The enumeration definition will either be in a C file, in which case the
const should be "static" because it is local to the file, or it will be
in a header, in which case making the small const "static" can be vastly
more efficient in many ways. I have been assuming that this definition
is at file scope, as most type declarations are, rather than local to a
function. If it is local to a function, then decent compilers will give
the same resulting code with or without the static.
But I see I have made a mistake here - I have mixed up with C++. You
can't use static_assert in C with a "const" like this for compile-time
checking. So in C, prior to C23, you actually need to write (with the
+1 correction to my original example) :
enum E1 { A, B, C, D };
enum { count_of_E1 = D + 1 };
if you want to be able to write :
_Static_assert(count_of_E1 == 4, "Checking enumeration");
With C23, you could have "constexpr int count_of_E1 = D;" and use it in
a static assertion. (constexpr declarations always have internal
linkage, so no "static" is needed.)
[toc] | [prev] | [next] | [standalone]
| From | antispam@fricas.org (Waldek Hebisch) |
|---|---|
| Date | 2025-10-23 23:23 +0000 |
| Message-ID | <10deddt$1tjqm$1@paganini.bofh.team> |
| In reply to | #394682 |
Thiago Adams <thiago.adams@gmail.com> wrote:
> On 10/23/2025 12:06 PM, David Brown wrote:
>> On 23/10/2025 13:03, Thiago Adams wrote:
>>> On 10/23/2025 4:12 AM, David Brown wrote:
>>>> On 22/10/2025 20:22, Kaz Kylheku wrote:
>>>>> On 2025-10-22, Thiago Adams <thiago.adams@gmail.com> wrote:
>>>>>> On 10/22/2025 8:44 AM, Richard Harnden wrote:
>>>>>> ....
>>>>>>>> Your program fragment is well defined.
>>>>>>>>
>>>>>>>> What the poster certainly tried to express was that in case you
>>>>>>>> haven't implemented a complete list of all possible cases and
>>>>>>>> also not provided a 'default' to catch all non-specified cases,
>>>>>>>> then you might get in troubles with your program, probably by
>>>>>>>> possible oversights, future extensions, new data, and whatnot.
>>>>>>>>
>>>>>>>> Personally I have the habit to always define a default branch,
>>>>>>>> and even if that default is impossible to reach you'll find an
>>>>>>>> error message (like "internal error with unexpected value...")
>>>>>>>> generated at that place.
>>>>>>>>
>>>>>>> Use an enum, and the compiler will warn you ...
>>>>>>>
>>>>>>> $ cat x.c
>>>>>>> #include <stdio.h>
>>>>>>>
>>>>>>> enum x {A, B, C};
>>>>>>>
>>>>>>> int main(void)
>>>>>>> {
>>>>>>> enum x x = C;
>>>>>>>
>>>>>>> switch (x)
>>>>>>> {
>>>>>>> case A:
>>>>>>> printf("A\n");
>>>>>>> break;
>>>>>>>
>>>>>>> case B:
>>>>>>> printf("B\n");
>>>>>>> break;
>>>>>>> }
>>>>>>>
>>>>>>> return 0;
>>>>>>> }
>>>>>>>
>>>>>>> $ gcc -Wall x.c
>>>>>>> x.c: In function ‘main’:
>>>>>>> x.c:9:9: warning: enumeration value ‘C’ not handled in switch [-
>>>>>>> Wswitch]
>>>>>>> 9 | switch (x)
>>>>>>> | ^~~~~~
>>>>>>>
>>>>>>>
>>>>>>
>>>>>> The problem with this GCC approach is when there are many enumerators
>>>>>> but only a few are used.
>>>>>
>>>>> The problem with the C and GCC approach is that there is no
>>>>> one-size-fits all solution.
>>>>>
>>>>> Some switches are intended to be exhaustive, such that
>>>>> missing a case is a bug.
>>>>>
>>>>> Some are not.
>>>>>
>>>>> You need an "eswitch" for the exhaustively handled enumerations, and
>>>>> switch for the others.
>>>>>
>>>>> GCC can turn on diagnostics over ranges of a file with pragma
>>>>> and there is also _Pragram, but it's all too clumsy.
>>>>>
>>>>
>>>> The gcc approach works fine in almost all situations - use "-
>>>> Wswitch=error", and add a default case if your switch is not meant to
>>>> handle all enumeration values. If the default should do nothing,
>>>> it's just "default: // Not all cases need handling". If the default
>>>> should never happen, "default: __builtin_unreachable();" or "default:
>>>> __builtin_trap();" might be appropriate.
>>>>
>>>>
>>>>
>>>
>>> But then instead a compiler time error (like I suggest) you leave it
>>> for runtime.
>>>
>>>
>>
>> As I said - use "-Wswitch=error". That gives you a compile-time error -
>> not merely a warning, as you had suggested. But /if/ your switch is not
>> meant to handle all cases, which was what Kaz was complaining about,
>> then you add a default case. I agree with you that a compile-time error
>> is best when possible, which is why that was my suggestion.
>>
>>
>>
>
>
> I think my sample is not covered by any GCC flag
>
>
> I will copy past it again:
> --------
> The problem with this GCC approach is when there are many enumerators
> but only a few are used.
>
> For instance :
>
> enum E {A, B, C /*, ...*/, Z};
>
> 1)
> void f(enum E e)
> {
> switch (e)
> {
> //used
> case A:
> case B:
> break;
>
> //NON USED
> case C:
> ...
> case Z:
> break;
> };
> }
>
> The problem with (1) is when we have too many
> non used enumerators, it is impractical to have a lot of switch cases.
>
> One alternative is to use default for all the non used:
>
> 2)
> void f(enum E e)
> {
> switch (e)
> {
> //used
> case A:
> case B:
> break;
>
> //NON USED (all others)
> default:
> break;
> };
> }
>
>
> The problem with (2) is when we add a new enumerator and
> this new enumerations should be used, but there is no warning and it
> goes accidentally for default.
>
>
> Solution?
>
> In C2Y the new keyword _Countof was introduced.
> It works returns the number of elements of array. IT IS FOR ARRAY ONLY.
>
> I did an EXTENSION in my compiler where _Countof(enum E) also returns
> the number of enumerators.
>
>
> enum E2 {A, B};
> static_assert(_Countof(enum E2) == 2);
>
> (It also could be a new keyword.
> static_assert(_EnumCount(enum E2) == 2);)
>
> Having this we can do:
>
> 3)
> void f(enum E e)
> {
> switch (e)
> {
> //used
> case A:
> case B:
> break;
>
> default:
> static_assert(_EnumCount(enum E2) == 20);
> break;
> };
>
> }
>
> Then when adding a new enumerator the programmer will have to review
> this code and update to 21 if it is not used, or handle it in a new case.
This helps catch some switches that need attention, but will miss
situations where you need to change handling of some existing code,
more precisely, when you need to remove case from some switches and
add it to different ones.
In similar cases I usually depend on a different approach: I am
writing my types in a way that can be found by textual searches.
If there is any change to usage pattern I search for all uses
of given type (I may also wrap enum inside otherwise useless
struct to make sure that all uses need declaration).
Let me mention variation of technique proposed by David Brown.
Namely, for me reasonably typical usage pattern is when I
have a few ranges of enum, each with somewhat different usage.
For example, in a compiler I need codes for terminals and
nonterminals. But there are places when I need to handle both,
while in other places only terminals (or maybe nonterminals)
make sense. In such case one can define things like
'last_terminal' and 'last_sym_code' (and possibly 'first_nonterminal')
and test in appropriate places that codes are in expected
range. In Pascal one can define new types as subranges of
bigger enum type, IIUC in C this is not possible.
> This is also useful in other scenarios. For instance:
>
>
> enum E parse_enum_e(const char* s)
> {
> if (strcmp(s, "A") == 0) return A;
> if (strcmp(s, "B") == 0) return B;
> if (strcmp(s, "C") == 0) return C;
> if (strcmp(s, "D") == 0) return D;
> if (strcmp(s, "E") == 0) return E;
> if (strcmp(s, "F") == 0) return F;
> static_assert(_Countof(enum E) == 6);
>
> return A;
> }
>
> If a new enumerator is added we need to include it.
In similar context I would probably use table of pairs + GNU
tyle macrology. That is have "codes.h" with content like
M(A)
M(B)
...
M(F)
and in source file have
enum codes {
#define M(x) x,
#include "codes.h"
#undef M
};
struct {char * name; enum codes code;} name_tab[] = {
#define M(x) {#x, x},
#include "codes.h"
#undef M
};
Or possibly have an external program to generate needed C code.
The point is that when usage is consistent with a simple pattern
it is easier to make sure that code is correct "by construction"
than to depend on language features to catch errors in manually
maintained code.
--
Waldek Hebisch
[toc] | [prev] | [next] | [standalone]
| From | David Brown <david.brown@hesbynett.no> |
|---|---|
| Date | 2025-10-24 09:53 +0200 |
| Message-ID | <10dfb9n$2eoat$1@dont-email.me> |
| In reply to | #394695 |
On 24/10/2025 01:23, Waldek Hebisch wrote:
> Thiago Adams <thiago.adams@gmail.com> wrote:
>> On 10/23/2025 12:06 PM, David Brown wrote:
>>> On 23/10/2025 13:03, Thiago Adams wrote:
>>
>> Then when adding a new enumerator the programmer will have to review
>> this code and update to 21 if it is not used, or handle it in a new case.
>
> This helps catch some switches that need attention, but will miss
> situations where you need to change handling of some existing code,
> more precisely, when you need to remove case from some switches and
> add it to different ones.
>
> In similar cases I usually depend on a different approach: I am
> writing my types in a way that can be found by textual searches.
> If there is any change to usage pattern I search for all uses
> of given type (I may also wrap enum inside otherwise useless
> struct to make sure that all uses need declaration).
Since you mention "textual searches", one technique that can sometimes
be helpful when you change a definition and want to be sure that you
have changed or checked all uses is to change the names in the
declaration. While we have been using "enum E { A, B, C };" in these
posts, real code typically has longer names - perhaps "enum run_states",
or whatever, with either a prefix or a postfix to the enumeration type
and the enumerators. If you change that in your declaration, such as by
adding an X somewhere, you are guaranteed compile-time errors until you
have gone through all the uses in the code and added the X as you
checked the use-cases and changed or added as appropriate. Once
everything is complete, a big search-and-replace can remove the X's.
[toc] | [prev] | [next] | [standalone]
Page 1 of 2 [1] 2 Next page →
Back to top | Article view | comp.lang.c
csiph-web