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 说它不应该。你付钱,然后选择。
如果您愿意,可以使用ignore
或warning
代替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]();
}
如果我希望我的 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 说它不应该。你付钱,然后选择。
如果您愿意,可以使用ignore
或warning
代替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]();
}