#c
#c
Вопрос:
Взгляните на следующий код:
typedef enum {
A, B, C
} Foo;
int foo(Foo x) {
switch(x) {
case A: return 1;
case B: return 2;
case C: return 3;
}
}
GCC 10.2 выводит
<source>:11:1: warning: control reaches end of non-void function [-Wreturn-type]
11 | }
| ^
Это потому, что я могу передать что-то вроде 42 foo
, а не только A
, B
, или C
. Итак, вопрос: как сообщить GCC, что только A
, B
, или C
может обрабатываться оператором switch, в противном случае поведение не определено? Приемлема функциональность, зависящая от компилятора.
Позвольте мне указать на некоторые решения, которые меня не удовлетворяют. Во-первых, я мог бы просто вставить, default: __builtin_unreachable();
но это повлияло бы на анализ прецедентов: представьте, что, по-видимому, я добавляю D
вариант, и компилятор не скажет мне, что D
это необработанный.
Во-вторых, я мог бы вставить if (x > C) { __builtin_unreachable(); }
перед оператором switch , но это слишком невозможно, потому switch(x)
что на самом деле генерируется макрокомандой, которая не знает о вариантах Foo
, она не знает ничего, кроме некоторой переменной x
.
В-третьих, я мог бы вставить #pragma GCC diagnostic ignored "-Wreturn-type"
, но опять же, switch(x)
генерируется макрокомандой, и именно поэтому я не могу вернуть диагностику в предыдущее состояние с помощью #pragma GCC diagnostic pop
.
В-четвертых, я мог бы использовать массив вместо switch
, но возвращаемые выражения не являются постоянными и предоставляются пользователем, генерирующим макрос switch(x)
.
И последнее: я мог бы написать return 42;
после оператора switch, но опять же я хочу автоматически отключить предупреждение внутри генерации макроса switch(x)
, потому что оно широко используется в моей кодовой базе.
Комментарии:
1. Может быть, добавить
default:
регистр?2. @Code-Apprentice: ОП уже озвучил эту идею.
3. Можете ли вы изменить макрос, чтобы присваивать переменную вместо возврата? Затем вы можете вернуть переменную после переключения.
4. @JonathanLeffler, выражения могут быть произвольно сложными и иметь визуальные эффекты, как я указал в вопросе.
5. К сожалению, в C нет концепции строгих перечислений. Вам придется пойти на некоторые уступки.
Ответ №1:
Если вам хочется заняться легким мазохизмом, вы могли бы грубо реализовать поведение, подобное исключению, используя setjmp
и longjmp
:
#include <stdlib.h>
#include <setjmp.h>
#include <stdio.h>
typedef enum Foo {
A, B, C
} Foo;
#define ENUM_OUT_OF_RANGE -1
jmp_buf env;
int foo( Foo x )
{
switch ( x )
{
case A: return 1;
case B: return 2;
case C: return 3;
default: longjmp( env, ENUM_OUT_OF_RANGE ); // "throw" ENUM_OUT_OF_RANGE
}
}
int main( void )
{
int ex;
if ( (ex = setjmp( env )) == 0 ) // "try" block
{
Foo arr[] = {A, B, C, 42};
for ( size_t i = 0; i < 4; i )
{
printf( "value of %d = %dn", arr[i], foo( arr[i] ) );
}
}
else // "catch" block
{
if ( ex == ENUM_OUT_OF_RANGE )
{
fprintf( stderr, "foo was called with a value outside the range of Foon" );
}
}
return 0;
}
Сборки без предупреждений следующим образом (по крайней мере, в моей системе):
gcc -o exceptions -std=c11 -pedantic -Wall -Werror exceptions.c
Вывод:
$ ./exceptions
value of 0 = 1
value of 1 = 2
value of 2 = 3
foo was called with a value outside the range of Foo