问题 在交换机中捕获混合枚举


在一些遗留代码中,我有很多枚举,以及一​​个巨大的切换案例。我想测试一下开关是否具有纯枚举类型。无意义的例子:

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或类似的工具可以做到这一点,我也会很高兴。


975
2018-01-16 08:39


起源

尝试在现有程序中就地执行此操作并不是一个好主意。将switch语句复制/粘贴到文本文件中,写一个知道给定枚举声明的程序/脚本,然后搜索每个 case 并查看以下常量是否为列出的常量之一。 - Lundin
@Lundin如果它只是一个开关那么简单,但我有几个开关内有开关的地方。所以当然可以做到,但它需要一些解析功能。 - Otzen
您需要一个包含枚举声明的文件和一个包含该开关的文件。那里几乎没有火箭科学,只需要进行简单的文本搜索 case。发明一些“更聪明”的东西会花费更长的时间,而这只是你对该特定项目只会做一次的事情。 - Lundin
@Lundin很抱歉没有明确上下文。我有很多带有更多嵌套交换机的文件,而且我不会这样做一次。我需要不断检查是否有人违反了这条规则。许多开发人员(不同技能水平)正在开发代码库。 - Otzen
@Otzen:这可能是一个荒谬的想法,但你不能用C ++编写一些软件堆栈,在那里可能会导致更多的编译时失败? - Bathsheba


答案:


不,你不能限制 switch  case 标签到特定的显式值 enum。 (您可以在C ++中对C ++ 11感兴趣)。

如果你能改变 enum 值因此它们不相交,这可能对您有所帮助,但仅限于运行时。


7
2018-01-16 08:45



现在我再次访问标准并弄清楚它是如何完成的。这就是为什么我喜欢你的答案,并允许自己嘲笑袜子机器人。 - StoryTeller
@StoryTeller:我猜6.4.2第二点是我们所喜欢的 其中constant-expression应为切换条件的提升类型的转换常量表达式(5.19)。在转换为相同的开关后,同一开关中的两个外壳常数应具有相同的值 推广类型 切换条件。 这是它与C标准不同的地方 - coderredoc
出于对C ++ 11的兴趣。那么替代方案是什么? - ネロク
@coderredoc - 这个想法是为了防止促销和隐含转换整体。并且在交换条件中使用范围枚举类型的表达式就足够了。因此,只有范围内的枚举器才能轻松显示为案例标签。我有点需要检查:) - StoryTeller
@Otzen:我不知道是谁告诉你条件表达式中的任务是不好的做法,但那是纯粹的肆无忌惮的教条主义。适当使用它是一个强大的结构。与I / O库特别有效。 - Bathsheba


标准 到目前为止只有一个约束标记语句

每个案例标签的表达 应该是一个整数常量   表达式并没有两个案例常量表达式相同   转换后,switch语句应具有相同的值

只要它是一个整数常量表达式,它们是否属于不同的枚举并不重要。是的 你不能 做你想做的事 C


3
2018-01-16 08:46





来自 文件 上 -Wswitch-enum (假设您使用的是GCC): “使用此选项时,枚举范围之外的案例标签也会引发警告。” AFAICK,此开关未启用 -Wall 要么 -Wextra


0
2018-01-16 15:51



我也试过了这个选项,它只会警告“外来”枚举是否在切换枚举范围之外有整数值。 - Otzen


case xxx 是一个简单的关键字,具有不那么硬的典型语法。在捏时,应该可以抓住  通过正则表达式出现它。表达的第一个候选人就像是

(^|\s)case\s+[^:]+:
             \---/anything terminated by colon
\----/drop things like 'uppercase'

这将通过文件捕获大多数(如果不是全部)典型的case关键字。然后,检测切换关键字:

)\s*{\s*case\s

应该这样做。虽然它不会寻找 switch 关键字,它检测第一个关闭括号 case。恕我直言,足够接近,应该在大多数情况下工作。

能够检测到 case 和 switch 和它们的位置,您可以通过前面的开关对案例进行分组,并执行案例值的验证。

那当然意味着你必须编写一个可以做到这一点的小工具,但对我来说这听起来像是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"
}

所以这不是一个完美的解决方案,但如果您的开关/外壳是“干净的”(没有这样的宏,等等),那么值得考虑。


0
2018-01-16 09:04



注意:我标记了它[c]而不是[c ++] - Otzen
@Otzen:嗯......对,我错过了。 IIRC使第一个正则表达式变得更加简单。由于其余几乎只是草图,我没有看到任何其他影响 - quetzalcoatl


好吧,我会自己回答。 经过一些研究后我得出结论,至少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甚至没有抱怨在交换机中丢失枚举,显然它只查看数值,而不是查看提供的枚举。

我用pc-lint测试,发现两者

    ---模块:t.c(C)
                   _
            案例TO:
    t.c 23警告408:类型与开关表达式不匹配
        _
        }
    t.c 26 Info 787:enum常量'EN :: ONE'未在开关内使用

不幸的是,这不是我希望的答案,编译器完成这项工作会更好,而不是另一种工具。

仍然愿意为其他人提供更好的答案。


0
2018-01-26 11:10