Подтвердить что ты не робот

Поймать смешанные перечисления в переключателе

В некотором унаследованном коде у меня много перечислений и огромные случаи переключения. Я хотел бы проверить, что коммутаторы имеют чистые типы перечислений. Пример бессмыслицы:

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 или подобному, может это сделать, я тоже был бы счастлив.

4b9b3361

Ответ 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.