switch case:case标签不会减少为整数常量

问题描述 投票:3回答:5

我完全了解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_int26984((union my_union) { "no" }).my_int28526。但是我不能自己写这些数字,因为它们取决于机器的字节顺序(显然我的机器是小端的)。但编译器知道后者并且在编译期间确切地知道((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 switch-statement
5个回答
1
投票

关于为什么C标准不允许编译器接受的问题

case ((struct my_struct) { 4 }).my_int:

......我们无法肯定地回答这个问题,因为这里没有人在C委员会(据我所知,无论如何)这个设计决定是在30年前制定的,所以有一个不错的机会没有人在那里记得理由。

但我们可以说这些事情:

  1. 最初的1989 C标准故意遗漏了可以实现的任何数量的功能,但只是在实现复杂性,编译时内存要求等方面的成本很高。例如,区分“常量表达”和“整数常量表达式“在标准中是编译器不应该在编译时进行浮点运算。 您要求的功能大致与实施一样困难 static const int CONSTANT = 123; ... switch (x) { case CONSTANT: ... } 这也不需要在C中工作(尽管它是在C ++中)。
  2. 自1989年以来对C标准的增加相对较小,仅仅是为了满足大量需求。特别是,据我所知,“实现此功能不再昂贵”并不足以成为理由。

这是我能给你的最佳答案。


5
投票

虽然表达式((struct my_struct) { 4 }).my_int确实在运行时被评估为4,但它不是常数。 switch-case需要一个恒定的表达式。

我看到你已经宣布my_intconst。但这只意味着以后不能修改它。这并不意味着表达式((struct my_struct) { 4 }).my_int是常数。

如果你使用if-statement而不是switch-case,你会没事的。

if (((struct my_struct) { 4 }).my_int == 4) {
    printf("Hey there!\n");
}

4
投票

case语句中的switch标签需要一个整数常量表达式,定义如下:

整数常量表达式应具有整数类型,并且只能具有整数常量的操作数,枚举常量,字符常量,结果为整数常量的sizeof表达式,_Alignof表达式以及作为强制转换的直接操作数的浮点常量。整数常量表达式中的转换运算符只能将算术类型转换为整数类型,除非作为sizeof或_Alignof运算符的操作数的一部分。

表达式((struct my_struct) { 4 }).my_int不符合此定义的整数常量表达式,即使它是一个整数值表达式,其值可以在编译时确定。


1
投票

这是最低的共同点。

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内)。


0
投票

好。为什么不可能看起来已经彻底解释了......我会把它留在这里,然后。

void *

clang 9,arch-linux,x86_64

© www.soinside.com 2019 - 2024. All rights reserved.