C: 关闭-Wswitch 怎么会编译失败?

C: how can I fail to compile when -Wswitch is off?

如果我希望我的 switch(an_enum) 语句在遗漏枚举大小写时得到报告,我可以打开 -Wswitch 编译器标志 (on gcc)。

enum E { e1, e2, e3 };

...
switch(e) {
  case e1: ...
  case e2: ...
  // NO default: checked by -Wswitch and -Werror
}

效果很好:"error: enumeration value ‘e3’ not handled in switch [-Werror=switch]"

但现在我的代码的正确性取决于使用的编译器标志,这有点脆弱。

像这样:

switch(e) __attribute__((exhaustive))
{
  ...
}

如果 -Wswitch 标志关闭,有没有办法让那段代码失败?或者在代码中临时打开它?

可靠且便携的解决方案是在 switch 语句中添加一个 default 标签。它可以做:

default:
    assert(0);
    abort();

这样即使断言被禁止它也会失败——或者如果您愿意,您可以做一些更明智的事情。

如果您使用的是 GCC,则可以使用 diagnostic pragmas 来获得为短范围打开 -Wswitch 所需的结果。

#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic error "-Wswitch"
#endif /* __GNUC__ */

switch (e)
{
…
}

#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif /* __GNUC__ */

这会推送诊断状态,将其更改为添加错误,就像在命令行上指定 -Wswitch 一样,编译 switch 语句,然后弹出(恢复)保存的诊断状态。

如果您从不使用 GCC(或者可能是 Clang)以外的编译器,则可以省略 __GNUC__ 测试。然而,其他平台上的编译器(例如 AIX 7.2 上的 IBM XLC 13.1.3)抱怨特定于 GCC 的编译指示,即使 C standard 说它不应该。你付钱,然后选择。

如果您愿意,可以使用ignorewarning代替error来实现其他效果。

您可能还会注意到 -Wswitch 选项是 -Wall 启用的警告之一 — 如果您使用 GCC 进行编译,则应始终使用 -Wall(和 -Wextra-Werror),所以你永远不应该 运行 遇到问题。

此外,程序的正确性取决于您编写(或修改)的方式,而不是编译器选项。取决于编译器选项的是你所做的错误是否被编译器发现。

假设枚举仅包含您示例中的相邻数字,那么您可以 "unroll" 将 switch 语句替换为 table 函数指针,这是相当常见的做法。函数指针 table 将是纯标准 C。示例:

typedef enum 
{ 
  e1, 
  e2, 
  e3,
  eN  // enum counter
} e_t;

typedef void e_func_t (void);

e_func_t* const do_stuff [] =   // array of functions of type void f(void);
{
  e1_stuff, 
  e2_stuff,
  e3_stuff,
};

// force compilation error if missing a "case statement":
_Static_assert(sizeof(do_stuff)/sizeof(*do_stuff) == eN, 
               "Function pointer table incomplete.");

然后你可以用这个替换整个开关:

if(e < e1 || e >= eN)
{  
  /* handle errors or "default" here */ 
}
else
{
  do_stuff[e]();
}