修改Duff设备的语法 - 这是合法的C/C++吗?

8

昨晚我第一次遇到了奇怪的达夫设备。我已经阅读了一些相关资料,我认为它并不难理解。但我对其中的奇怪语法很好奇(来自维基百科):

register short *to, *from;
register int count;
{
  register int n = (count + 7) / 8;
  switch(count % 8) {
  case 0: do {    *to = *from++;
  case 7:         *to = *from++;
  case 6:         *to = *from++;
  case 5:         *to = *from++;
  case 4:         *to = *from++;
  case 3:         *to = *from++;
  case 2:         *to = *from++;
  case 1:         *to = *from++;
    } while(--n > 0);
  }
}

我正在阅读C++标准中有关switch语句的定义(如果过时了,请告知我,我不熟悉Open-Std.org)。据我所理解,case语句只是由switch语句使用的简化跳转语句。

switch本身完全忽略了嵌套do-while,而循环则忽略了case语句。因为switch在循环内进行跳转,所以循环被执行。switch用于处理剩余部分(通过8的除法得到),而循环则处理均匀可被整除的部分。这一切都很合理。

我的问题是为什么会出现这样笨拙的语法?我想到的是循环可以被写成包含所有case语句的形式,是这样吗?我在标准中没有看到任何禁止这种行为的内容,并且它在GCC 4.7下编译正确,那么以下代码是否被认为是合法的呢?

register short *to, *from;
register int count;
{
  register int n = (count + 7) / 8;
  switch (count <= 0 ? 8 : count % 8)
  {
    do
    {
      case 0:         *to = *from++;
      case 7:         *to = *from++;
      case 6:         *to = *from++;
      case 5:         *to = *from++;
      case 4:         *to = *from++;
      case 3:         *to = *from++;
      case 2:         *to = *from++;
      case 1:         *to = *from++;
      default:        ; // invalid count, suppress warning, etc.
    } while(--n > 0);
  }
}

对我来说,这使得代码的意图更加清晰。感谢任何反馈。;)

编辑:如下所述,原始代码是用C编写的,countn变量具有隐式int类型。由于我标记了它为C ++,因此我进行了修改。

编辑2: 修改了示例代码以解决无效计数值的问题。

3个回答

8
看C++11标准,我认为您在询问的代码部分是被允许的。您尝试过吗?
我看到最适用的规则是:
注意:通常,switch的子语句是复合语句,case和default标签出现在该子语句包含的顶层语句中,但这并不是必需的。
实际上,这意味着您可以去掉do-while周围的大括号,写成:
  int n = (count + 7) / 8;
  switch (count % 8) do
  {
      case 0:         *to = *from++;
      case 7:         *to = *from++;
      case 6:         *to = *from++;
      case 5:         *to = *from++;
      case 4:         *to = *from++;
      case 3:         *to = *from++;
      case 2:         *to = *from++;
      case 1:         *to = *from++;
  } while(--n > 0);

然而,这行代码并不是有效的C++代码:

register n = (count + 7) / 8;

C++不允许默认整型,变量的类型必须被指定或推断出来。


这里,请修复迭代次数而不破坏格式:

  int n = 1 + count / 8;
  switch (count % 8) do
  {
                      *to = *from++;
      case 7:         *to = *from++;
      case 6:         *to = *from++;
      case 5:         *to = *from++;
      case 4:         *to = *from++;
      case 3:         *to = *from++;
      case 2:         *to = *from++;
      case 1:         *to = *from++;
      case 0:                      ;
  } while(--n > 0);

1
截至1999年的ISO标准,C语言也不允许默认的int。 (Duff在C99之前就设计了他的设备。) - Keith Thompson
@Keith:是的,早期编译器太蠢了,除非你要求,否则无法将本地变量存入寄存器。 - Ben Voigt
2
我想知道switch-do语句是否会像操作符<---->一样被添加到C/C++的鲜为人知的特性列表中。 - Ben Voigt
1
@BenVoigt:不要忘记&&&运算符 - Keith Thompson
只是一些额外的反馈,我一直在阅读关于除法(显然)是多么低效的内容,所以我想到只要未展开循环的次数是2的幂(例如在这种情况下,它是8),则可以用二进制右移三位替换除法,用二进制与七替换模数。现在对我来说不是问题,但可能有助于性能关键的代码……? - monkey0506
显示剩余2条评论

1

代码肯定是合法的:没有任何关于块和/或循环的要求。值得注意的是,上面的循环不能正确处理count == 0。然而,下面的循环可以正确处理:

int count = atoi(ac == 1? "1": av[1]);
switch (count % 4)
{
case 0: while (0 < count) { std::cout << "0\n";
case 3: std::cout << "3\n";
case 2: std::cout << "2\n";
case 1: std::cout << "1\n";
    count -= 4;
    }
}

case 0标签放在循环内部也会错误地执行嵌套语句。虽然我看到Duff的设备总是使用do-while循环,但似乎上述代码更自然地处理边界条件。

我能理解以上代码并不能完全处理这种情况,但你的代码存在一些丑陋之处。虽然标准并不要求,但即使在这种情况下,我仍然认为switch语句应该有一个默认情况,这是良好的编程风格。我可以将无效的count值委托给默认情况:switch (count <= 0 ? 8 : count % 8) do { /* ...case 0, 7, 6, 5... (因SO评论格式而转换为块注释) */ default: ; } while (--n > 0);这样可以避免丑陋的代码,并正确处理每种情况。 - monkey0506

1

是的,这是合法的。 switch 语句的标签通常与 switch 语句在同一级别上编写。但是,在复合语句中编写它们是合法的,例如在循环中间。

编辑:switch 语句的主体没有必要以标签开头,任何代码都是合法的。但是,从 switch 语句本身没有进入它的方法,因此除非它是循环或普通标签,否则该代码将无法访问。

switch 语句的另一个有趣之处是花括号是可选的。但是,在这种情况下,只允许一个标签:

  switch (i)
      case 5: printf("High five\n");

1
问题是受控块是否必须在第一条语句上具有casedefault标签。答案似乎是“不需要”。(您的摘要是正确的) - Ben Voigt
所以,你可以使用switch (i) case 42: single_stmt;代替编写if (i == 42) single_stmt;的方式。 - md2perpe

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