Поймать смешанные перечисления в переключателе
В некотором унаследованном коде у меня много перечислений и огромные случаи переключения. Я хотел бы проверить, что коммутаторы имеют чистые типы перечислений. Пример бессмыслицы:
typedef enum EN
{
EN_0,
EN_1
} EN_T;
typedef enum DK
{
DK_0,
DK_1
} DK_T;
EN_T bar = ...
switch( bar )
{
case EN_0:
...
break;
case DK_1: //<-- mixed type
...
break;
}
Я попытался скомпилировать это с помощью gcc with -Wall -Wextra -pedantic
и не получил никаких предупреждений. Любые идеи о том, как проверить это? Либо как предупреждения компилятора или выделенный тестовый код. Поскольку оба переключателя и перечисления имеют более 100 членов, он должен быть общим для некоторого уровня.
Изменить: Пожалуйста, обратите внимание, что меня не волнует, если это законно c, в соответствии со стандартом C.
Это плохая практика, и компилятор может предупредить о плохой практике или потенциальных ошибках, которые не нарушают стандарт, например, if( a = 1)...
всегда будет правдой, совершенно законным, но может быть ошибкой.
Я могу заставить компилятор предупредить, если переключатель в перечислении не содержит все значения этого перечисления a.s.o.
Предпочтительно, чтобы компилятор мог работать, но если инструмент, подобный lint или подобному, может это сделать, я тоже был бы счастлив.
Ответы
Ответ 1
Хорошо, я отвечу сам.
После нескольких исследований я пришел к выводу, что по крайней мере gcc не будет жаловаться на это, мне нужно использовать дополнительную программу, такую как pc-lint.
Я сделал небольшую переписку, чтобы подчеркнуть проблему.
#include <stdio.h>
typedef enum EN
{
ZERO,
ONE
} EN_T;
typedef enum DK
{
EN, /* Danish word for One */
TO /* Danish word for Two */
} DK_T;
char* E2str( EN_T en )
{
char* ret;
switch( en )
{
case ZERO:
ret = "0";
break;
case TO:
ret = "2";
break;
}
return ret;
}
int main( void )
{
printf( "0 = %s\n", E2str( ZERO ) );
printf( "1 = %s\n", E2str( ONE ) );
return 0;
}
Это будет компилироваться отлично, без предупреждений даже с:
gcc -o t.exe t.c -Wall -Wextra -pedantic
Выход будет:
0 = 0
1 = 2
Понятно, что этот вывод, вероятно, не был целью писателя. И да в этом маленьком примере это ясно и очевидно, когда просто смотришь на код. Но представьте, что это коммутатор с 200 + случаями, а переключатель содержит другие переключатели, а наименование перечисления не так ясно, как в моем примере в исходном вопросе. Почти невозможно обнаружить ошибки, подобные тем, которые приведены в этом примере.
Также обратите внимание, что с помощью -Wextra
я включаю проверку gcc, которая будет предупреждать, если у меня есть переключатель в перечислении, и случаи не содержат всех значений в этом перечислении. Но поскольку в перечислении TO
есть нормальное числовое значение как ONE
, gcc даже не жалуется на отсутствие Enums в коммутаторе, по-видимому, он смотрит только на числовое значение, а не на предоставленное перечисление для этой проверки.
Мой тест с pc-lint, отмеченный как
--- Module: t.c (C)
_
case TO:
t.c 23 Warning 408: Type mismatch with switch expression
_
}
t.c 26 Info 787: enum constant 'EN::ONE' not used within switch
К сожалению, это был не тот ответ, на который я надеялся, было бы гораздо приятнее сделать это с помощью компилятора, а не с помощью еще одного инструмента.
По-прежнему открыт, чтобы дать кому-то еще кредит на лучший ответ.
Ответ 2
Нет, вы не можете ограничивать метки switch
case
явными значениями конкретного enum
. (Вы можете в С++ из интереса от С++ 11).
Если вы можете изменить значения enum
, чтобы они не пересекались, это может помочь вам немного, но только во время выполнения.
Ответ 3
От standard есть только одно ограничение, помеченное тегом case
Выражение метки каждого случая должно быть целочисленной константой выражение и никакие два из постоянных выражений случая в одном и том же оператор switch должен иметь то же значение после преобразования.
Пока это целочисленное постоянное выражение, не имеет значения, принадлежат ли они к другим перечислениям или нет. Итак, да вы не можете делать то, что хотите, в C
.
Ответ 4
case xxx
- простое ключевое слово с нестандартным типичным синтаксисом. Если это ущемляет, должно быть возможно поймать большинство вхождения его регулярными выражениями. Первым кандидатом на выражение, которое приходит ко мне, является что-то вроде
(^|\s)case\s+[^:]+:
\---/anything terminated by colon
\----/drop things like 'uppercase'
Это уловило бы большинство, если не всех, типичных случаев ключевого слова case, хотя файл. Затем определите ключевые слова переключателя:
)\s*{\s*case\s
Это должно сделать это. Хотя он не будет искать ключевое слово switch
, он обнаруживает первую закрывающую скобку, которая перед первым case
. IMHO, достаточно близко и должен работать в большинстве случаев.
Возможность обнаруживать case
и switch
и их местоположение, вы можете группировать случаи по их предыдущему коммутатору и выполнять проверку значений case.
Это, конечно, означает, что вам нужно будет написать небольшую утилиту, которая будет делать это, но для меня это звучит как 50-100 строк без минимального кода.
Этот способ, конечно, не будет обрабатывать такие вещи, как:
// macros:
#define SAFE_SWITCH(x) switch(assert_non_negative(x)){
#define SWITCH_END }
SAFE_SWITCH(..) case BAR: .... SWITCH_END
// clever hacks from bored programmers:
switch(parser.nodetype)
{
default: throw ..;
#include "opcodes.inc"
#include "operands.inc"
#include "keywords.inc"
}
и т.д.. так что это не идеальное решение, но если ваш коммутатор/футляр "чистый" (таких макросов и т.д.), то стоит подумать.
Ответ 5
Из документация в -Wswitch-enum
(при условии, что вы используете GCC):
"Метки case за пределами диапазона перечисления также вызывают предупреждения при использовании этой опции". AFAICK, этот переключатель не включен с помощью -Wall
или -Wextra
.