我完全了解switch
语句背后的机制以及为什么需要整数常量。我不明白的是为什么以下case
标签不被视为整数常量。之后怎么样了?一个不存在的变量?有人可以归类吗? C编译器真的需要这么笨吗?
struct my_struct {
const int my_int;
};
switch (4) {
case ((struct my_struct) { 4 }).my_int:
printf("Hey there!\n");
break;
}
而且当然…
error: case label does not reduce to an integer constant
case ((struct my_struct) { 4 }).my_int:
编辑回答尤金的评论:
你真正的用例是什么?如果它是一个整数常量,为什么要这么复杂呢?
我试图找到一个聪明的黑客来切换两个字符的字符串,使用union
而不是struct
,如下例所示:
#include <stdio.h>
union my_union {
char my_char[sizeof(int)];
int my_int;
};
void clever_switch (const char * const my_input) {
switch (((union my_union *) my_input)->my_int) {
case ((union my_union) { "hi" }).my_int:
printf("You said hi!\n");
break;
case ((union my_union) { "no" }).my_int:
printf("Why not?\n");
break;
}
}
int main (int argc, char *argv[]) {
char my_string[sizeof(int)] = "hi";
clever_switch(my_string);
return 0;
}
...当然不能编译。
在我的机器上((union my_union) { "hi" }).my_int
是26984
而((union my_union) { "no" }).my_int
是28526
。但是我不能自己写这些数字,因为它们取决于机器的字节顺序(显然我的机器是小端的)。但编译器知道后者并且在编译期间确切地知道((union my_union) { "no" }).my_int
将是什么数字。
令人讨厌的是我已经可以做到,但只使用非常模糊(效率稍低)的语法。以下示例编译得很好:
#include <stdio.h>
void clever_switch (const char * const my_input) {
#define TWO_LETTERS_UINT(FIRST_LETTER, SECOND_LETTER) ((unsigned int) ((FIRST_LETTER) << 8) | (SECOND_LETTER))
switch (TWO_LETTERS_UINT(my_input[0], my_input[1])) {
case TWO_LETTERS_UINT('h', 'i'):
printf("You said hi!\n");
break;
case TWO_LETTERS_UINT('n', 'o'):
printf("Why not?\n");
break;
}
#undef TWO_LETTERS_UINT
}
int main (int argc, char *argv[]) {
clever_switch("hi"); /* "You said hi!" */
clever_switch("no"); /* "Why not?" */
return 0;
}
所以问题仍然存在:C编译器(或本例中的C标准)真的需要如此愚蠢吗?
关于为什么C标准不允许编译器接受的问题
case ((struct my_struct) { 4 }).my_int:
......我们无法肯定地回答这个问题,因为这里没有人在C委员会(据我所知,无论如何)这个设计决定是在30年前制定的,所以有一个不错的机会没有人在那里记得理由。
但我们可以说这些事情:
static const int CONSTANT = 123;
...
switch (x) { case CONSTANT: ... }
这也不需要在C中工作(尽管它是在C ++中)。这是我能给你的最佳答案。
虽然表达式((struct my_struct) { 4 }).my_int
确实在运行时被评估为4,但它不是常数。 switch-case需要一个恒定的表达式。
我看到你已经宣布my_int
为const
。但这只意味着以后不能修改它。这并不意味着表达式((struct my_struct) { 4 }).my_int
是常数。
如果你使用if-statement
而不是switch-case
,你会没事的。
if (((struct my_struct) { 4 }).my_int == 4) {
printf("Hey there!\n");
}
case
语句中的switch
标签需要一个整数常量表达式,定义如下:
整数常量表达式应具有整数类型,并且只能具有整数常量的操作数,枚举常量,字符常量,结果为整数常量的sizeof表达式,_Alignof表达式以及作为强制转换的直接操作数的浮点常量。整数常量表达式中的转换运算符只能将算术类型转换为整数类型,除非作为sizeof或_Alignof运算符的操作数的一部分。
表达式((struct my_struct) { 4 }).my_int
不符合此定义的整数常量表达式,即使它是一个整数值表达式,其值可以在编译时确定。
这是最低的共同点。
C标准表示((struct my_struct) { 4 }).my_int
不满足对案例标签施加的约束(即它们是整数常量表达式),因此兼容的C编译器不需要足够聪明以便能够对其进行优化。
该标准不禁止编译器对其进行优化。确实,优化它是铿锵的。
你的计划:
#include <stdio.h>
struct my_struct {
const int my_int;
};
int main()
{
switch (4) {
case ((struct my_struct) { 4 }).my_int:
printf("Hey there!\n");
break;
}
}
qazxsw poi,如果你把它与qazxsw poi结合起来,你会收到警告。
在其他情况下,比如在区分VLA和常规数组时,整数常量表达式与其他整数表达式之间的区别也会影响其他构造,如just works on clang或基于-pedantic
的跳转,如果它们跳入switch
的范围则会被禁止。同样,编译器可以折叠它并允许这样的跳转,只要至少进行一次诊断(clang警告折叠,而不是跳转)。
如果您确实使用了这些结构,并且您的编译器不会阻止您,那么您的程序将无法移植。
最后,整数的编译时常量也会影响某种情况下的类型。
我相信Linux内核使用类似的东西
goto
检测整数常量表达式(我听说,这是清除VLA的任务的一部分)。
这基于C标准规则,即等于0的整数常量表达式强制转换为VLA
是空指针常量,而强制转换为#define IS_CEXPR(X) _Generic((1? (void *) ((!!(X))*0ll) : (int *) 0), int*: 1, void*: 0)
的常规整数表达式只是一个空指针,即使表达式的值已知确定三元类型的规则然后区分(void*)
表达式和空指针常量,如果(void*)
是整数常量表达式则导致(void*)
被键入(1? (void *) ((!!(X))*0ll) : (int *) 0)
,否则为int *
。
大多数编译器可能不会让你轻易绕过类型系统违规(特别是在X
内)。
好。为什么不可能看起来已经彻底解释了......我会把它留在这里,然后。
void *
clang 9,arch-linux,x86_64