C/C++中的char数组switch case语句

8

我有几个数据结构,每个结构都有一个4字节的字段。

由于在我的平台上4个字节等于1个int,所以我想在case标签中使用它们:

switch (* ((int*) &structure->id)) {
   case (* ((int*) "sqrt")): printf("its a sqrt!"); break;
   case (* ((int*) "log2")): printf("its a log2!"); break;
   case (((int) 'A')<<8 + (int) 'B'): printf("works somehow, but unreadable"); break;
   default: printf("unknown id");
}

这会导致编译错误,告诉我case表达式不能缩小为int

我怎样才能使用有限大小的char数组,并将它们转换成数字类型以在switch/case中使用?


1
这是针对C++(如问题标题所述)还是针对C99(如标签中所述)?我不确定答案在两者之间是否有所不同,但在问题中看到两种不同的语言而没有明确的原因会让人感到困惑。 - hmakholm left over Monica
2
现在标题中有两种语言,其中一种在标签中重复了。我们是否可以从中推断出您并不是在谈论C++?那么为什么它还在标题中呢? - hmakholm left over Monica
你不太喜欢编写端序友好的代码吗? - Steve-o
为什么要编写混乱、易于破坏或不可移植的代码呢?直接使用分派表即可。 - Ed Heal
7个回答

5

按照视频编码中使用的FourCC代码,遵循确切的方法:

在C++中设置FourCC值

#define FOURCC(a,b,c,d) ( (uint32) (((d)<<24) | ((c)<<16) | ((b)<<8) | (a)) )

对于每个标识符,使用枚举类型或宏可能是一个好主意:

enum {
    ID_SQRT = FOURCC( 's', 'q', 'r', 't'),
    ID_LOG2 = FOURCC( 'l', 'o', 'g', '2')
};

int structure_id = FOURCC( structure->id[0], 
                           structure->id[1],
                           structure->id[2],
                           structure->id[3] );
switch (structure_id) {
case ID_SQRT: ...
case ID_LOG2: ...
}

2
问题在于switch语句的case分支需要一个常量值。特别地,需要在编译时就已知的常量值。字符串的地址在编译时是不知道的 - 链接器知道地址,但甚至最终地址也不确定。我认为最终重定位后的地址只有在运行时才能获得。
你可以将问题简化为:
void f() {
    int x[*(int*)"x"];
}

这会产生相同的错误,因为编译时不知道"x"字面量的地址。这与例如...不同。

void f() {
    int x[sizeof("x")];
}

由于编译器知道指针的大小(32位构建中为4个字节)。

现在,如何解决您的问题?我想到了两件事:

  1. 不要将id字段设置为字符串,而是设置为整数,然后在case语句中使用常量列表。

  2. 我怀疑您需要在多个地方执行像这样的switch,因此我的另一个建议是:首先不要使用switch根据结构的类型执行代码。相反,结构可以提供函数指针,可以调用该指针来执行正确的printf调用。在创建结构时,将函数指针设置为正确的函数。

这里是说明第二个想法的代码草图:

struct MyStructure {
   const char *id;
   void (*printType)(struct MyStructure *, void);
   void (*doThat)(struct MyStructure *, int arg, int arg);
   /* ... */
};

static void printSqrtType( struct MyStructure * ) {
   printf( "its a sqrt\n" );
}

static void printLog2Type( struct MyStructure * ) {
   printf( "its a log2\n" );
}

static void printLog2Type( struct MyStructure * ) {
   printf( "works somehow, but unreadable\n" );
}

/* Initializes the function pointers in the structure depending on the id. */
void setupVTable( struct MyStructure *s ) {
  if ( !strcmp( s->id, "sqrt" ) ) {
    s->printType = printSqrtType;
  } else if ( !strcmp( s->id, "log2" ) ) {
    s->printType = printLog2Type;
  } else {
    s->printType = printUnreadableType;
  }
}

有了这个,你的原始代码只需要执行以下操作:
void f( struct MyStruct *s ) {
    s->printType( s );
}

这样,您可以将类型检查集中在一个地方,而不是在代码中杂乱无章地使用大量的switch语句。


2

免责声明:仅供娱乐或学习目的使用,对于严肃的代码,请使用常见习惯用法,在一般情况下不要依赖编译器特定行为;如果必须这样做,不兼容的平台应该触发编译时错误或使用好的、通用的代码。


看起来标准允许按语法使用多字符字符常量。尚未检查以下内容是否真的合法。

~/$ cat main.cc

#include <iostream>

#ifdef I_AM_CERTAIN_THAT_MY_PLATFORM_SUPPORTS_THIS_CRAP
int main () {
    const char *foo = "fooo";
    switch ((foo[0]<<24) | (foo[1]<<16) | (foo[2]<<8) | (foo[3]<<0)) {
    case 'fooo': std::cout << "fooo!\n";  break;
    default:     std::cout << "bwaah!\n"; break;
    };
}
#else
#error oh oh oh
#endif

~/$ g++ -Wall -Wextra main.cc  &&  ./a.out
main.cc:5:10: warning: multi-character character constant
fooo!

编辑:看起来,在语法摘录下方直接有2.13.2字符字面量项目1

[...] 包含多个c-char的普通字符字面值是多字符字面值。多字符字面值具有int类型和实现定义的值。

但在第二个项目中:

[...] 包含多个c-char的宽字符字面值的值是实现定义的。

所以要小心。


2
我认为问题在于在C语言中,switch语句中的每个case标签必须是一个整数常量表达式。从C ISO规范§6.8.4.2/3可知:

每个case标签的表达式应该是一个整数常量表达式 [...]

(我强调了一下)

C规范随后将“整数常量表达式”定义为一个常量表达式,其中(§6.6/6):

一个整数常量表达式)应该具有整数类型,并且只能有整数常量、枚举常量、字符常量、结果为整数常量的sizeof表达式和作为强制转换的立即操作数的浮点常量作为操作数。 在整数常量表达式中,强制转换运算符只能将算术类型转换为整数类型,除了作为sizeof运算符的操作数之一。

(我再次强调一下)。这表明您不能将字符字面值(指针)强制转换为整数,在case语句中,因为这种转换在整数常量表达式中是不允许的。

直观地说,这可能是因为在某些实现中,生成的可执行文件中字符串的实际位置直到链接时才被指定。因此,如果标签依赖间接于这些字符串地址的常量表达式,编译器可能无法为switch语句生成非常好的代码,因为它可能会错过编译跳转表的机会,例如。这只是一个例子,但规范更严格的语言明确禁止您执行上述操作。

希望这能帮助你!


1

我最终使用了这个宏,类似于问题中的第3种情况或phresnels的答案。

#define CHAR4_TO_INT32(a, b, c, d) ((((int32_t)a)<<24)+ (((int32_t)b)<<16) + (((int32_t)c)<<8)+ (((int32_t)d)<<0)) 

switch (* ((int*) &structure->id)) {
   case (CHAR4_TO_INT32('S','Q','R','T')): printf("its a sqrt!"); break;
}

1

这是特别危险的,因为对齐问题:在许多架构中,int 是4字节对齐的,但字符数组不是。例如,在 sparc 上,即使此代码可以编译(它不能,因为字符串地址直到链接时才知道),它也会立即引发 SIGBUS


0

这更像是 C 而不是 C++。

整型字符4的联合 { int_32 x; char[4] y; }

联合声明,定义其成员从同一地址开始,从本质上为相同字节集提供不同类型。

int_char4 ic4; ic4.x 是一个整数,而 ic4.y 是指向字符数组第一个字节的指针。

既然你想学习,那么实现就取决于你。


2
在C99中,精确为32位的有符号整数类型被命名为int32_t,而不是int_32 - jwodder
我认为这并没有解决原帖提出的问题。你能详细说明一下吗? - templatetypedef
如何在case表达式中使用union,使其转化为常量表达式?(例如,将“sqrt”转换为相应的int?) - i_want_to_learn
法律上,您只能读取最近写入的一个联合值。也就是说,如果您写入 y[...],从 x 读取会产生未定义的行为。 - Sebastian Mach
1
另外:在C语言中声明数组的语法是type name [length],而不是type [length] name。此外,对于int_32 / int32_t的混淆,这足以让我给它一个踩下去的投票(顺便说一句,我希望每个人都像这样证明他的贬低)。 - Sebastian Mach
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接