为什么在C/C++中交织使用switch/for/if语句是有效的?

huangapple go评论92阅读模式
英文:

Why it is valid to intertwine switch/for/if statements in C/C++?

问题

I'm reading boost/asio/coroutine.hpp and cannot understand the implementation of BOOST_ASIO_CORO_REENTER and BOOST_ASIO_CORO_YIELD. The expanded form of

reenter (this) {
  yield ..
  yield ..
}

seems to be intertwined switch/if/for statements. I'm wondering how come this is valid C code? I wrote something similar (shown below) and found it compiles using gcc.

int main() {
  int a = 1;
  switch (a)
  case 0: if (1) a = 2;
  else case 1: for (;;) {
    case 3:
      break;
  }

  return 0;
}
英文:

I'm reading boost/asio/coroutine.hpp and cannot understand the implementation of BOOST_ASIO_CORO_REENTER and BOOST_ASIO_CORO_YIELD. The expanded form of

reenter (this) {
  yield ..
  yield ..
}

seems to be intertwined switch/if/for statements. I'm wondering how come this is valid C code? I wrote something similar (shown below) and found it compiles using gcc.

int main() {
  int a = 1;
  switch (a)
  case 0: if (1) a = 2;
  else case 1: for (;;) {
    case 3:
      break;
  }

  return 0;
}

答案1

得分: 3

以下是翻译好的部分:

原因是因为switch语句不是结构化控制流语句。相反,它们应该被视为静态分派的语法糖。分派意味着控制流被重定向,静态意味着编译器知道它被重定向到哪里。

所以你的代码

int a = 1;
switch (a)
case 0: if (1) a = 2;
else case 1: for (;;) {
  case 3:
    break;
}
return 0;

将被编译成大致等效的内容

int a = 1;
void *dest = dispatch(a, { case0_addr, case1_addr, case3_addr });
goto *dest;
case0_addr:
if (1) { 
  a = 2;
} else {
case1_addr:
  for (;;) {
    case3_addr:
    goto case_end;
  }
}
case_end:
return 0;

其中dispatch是编译器用于生成静态分派所需的机器代码的函数。由于所有分派值都是常量,编译器知道所有分派目标,它可以生成非常高效的机器代码。

至于为什么它是合法的,我猜原因是因为没有特别的理由让它非法。如所示,case语句只是goto标签,因此它们可以放在任何地方。

英文:

The reason is because switch statements aren't structured control flow statements. Instead, they should be considered as syntactic sugar for static dispatch. Dispatch means that the control flow is redirected and static means that the compiler knows where it is redirected to.

So your code

int a = 1;
switch (a)
case 0: if (1) a = 2;
else case 1: for (;;) {
  case 3:
    break;
}
return 0;

would be compiled into something roughly equivalent of

int a = 1;
void *dest = dispatch(a, { case0_addr, case1_addr, case3_addr });
goto *dest;
case0_addr:
if (1) { 
  a = 2;
} else {
case1_addr:
  for (;;) {
    case3_addr:
    goto case_end;
  }
}
case_end:
return 0;

where dispatch is a function the compiler runs for emitting the machine code required for the static dispatch. Since all dispatch values are constants and all dispatch targets are known by the compiler, it can generate very efficient machine code.

As for why it is legal, I guess the reason is because there's no particular reason for it to be illegal. As shown, case statements are just goto labels so they can be placed wherever.

答案2

得分: 2

从语法上讲,switch 语句的主体只是一个语句(通常是一个复合语句,但不一定是)6.8

语句:
标记语句
复合语句
表达式语句
选择语句
迭代语句
跳转语句

这些语句可以被标记6.8.1

标记语句:
标识符 : 语句
case 常量表达式 : 语句
default : 语句

示例:

switch(1) one: case 1: dothis();

如果是一个复合语句,那么每个子语句也可以递归地被标记。示例:

switch(x) {
    if(1) one: case 1: dothis();
    else case 0: orthis();  /*fallthru*/
    three: case 3: three();
}

语法上将 case/default 标签和常规标签视为相同,只有语义检查会验证 case/default 标签是否在 switch 内部。

从实现角度看,所有内容都编译成(扁平的)汇编语言。

例如:

if(test) YesBranch; else ElseBranch;

被展开成(伪汇编语言):

IF_NOT_THEN_GOTO(test, PAST_YES_BRANCH)
YesBranch
goto PAST_NO_BRANCH;
NoBranch
PAST_NO_BRANCH:;

并且在这种扁平的代码中没有理由不使用标签。

case/default 标签与常规标签几乎相同,只是它们通常也用于计算跳转。

英文:

Syntactically, the body of a switch is just a statement (usually, but not necessarily a compound statement)

6.8:

statement:
			   labeled-statement
			   compound-statement
			   expression-statement
			   selection-statement
			   iteration-statement
			   jump-statement

which may be labeled
6.8.1:

labeled-statement:
				identifier : statement
				case constant-expression : statement
				default : statement

Example:

switch(1) one: case 1: dothis();

If it is a compound statement, then each substatement recursively may also be labeled. Example:

switch(x) {
	if(1) one: case 1: dothis();
	else case 0: orthis();  /*fallthru*/
	three: case 3: three();
}

The syntax treats case/default-labels and regular labels the same, only the semantic check verifies that case/default-labels be inside a switch.

Implementation-wise, everything compiles into (flat) assembly.

E.g.

if(test) YesBranch; else ElseBranch;

is flattened into (pseudo-assembly)

IF_NOT_THEN_GOTO(test, PAST_YES_BRANCH)
YesBranch
goto PAST_NO_BRANCH;
NoBranch
PAST_NO_BRANCH:;

and there's no reason why anything in such flat code couldn't be labeled.

case/default labels are also just like regular labels except they're also used in (most usually) a computed jump.

huangapple
  • 本文由 发表于 2020年1月6日 16:35:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/59608918.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定