【问题标题】:How does the C code that prints from 1 to 1000 without loops or conditional statements work?没有循环或条件语句从 1 打印到 1000 的 C 代码如何工作?
【发布时间】:2011-12-17 18:38:52
【问题描述】:

我发现 C 代码是 prints from 1 to 1000 without loops or conditionals : 但我不明白它是如何工作的。任何人都可以通过代码并解释每一行吗?

#include <stdio.h>
#include <stdlib.h>

void main(int j) {
  printf("%d\n", j);
  (&main + (&exit - &main)*(j/1000))(j+1);
}

【问题讨论】:

  • 您是编译为 C 还是 C++?你看到什么错误?你不能在 C++ 中调用 main。
  • @ninjalj 我创建了一个 C++ 项目并复制/粘贴错误代码:非法,左操作数的类型为 'void (__cdecl *)(int)' 并且表达式必须是指向完整的对象类型
  • @ninjalj 这些代码在 ideone.org 上运行,但在 Visual Studio 中不运行ideone.com/MtJ1M
  • @oussama 类似,但有点更多难以理解:ideone.com/2ItXm不客气。 :)
  • 我已从这些行中删除了所有 '&' 字符 (&main + (&exit - &main)*(j/1000))(j+1);并且这段代码仍然有效。

标签: c function-pointers


【解决方案1】:

永远不要写那样的代码。


对于j&lt;1000,j/1000 为零(整数除法)。所以:

(&main + (&exit - &main)*(j/1000))(j+1);

相当于:

(&main + (&exit - &main)*0)(j+1);

这是:

(&main)(j+1);

用j+1调用main。

如果j == 1000,则输出相同的行:

(&main + (&exit - &main)*1)(j+1);

归结为

(&exit)(j+1);

这是exit(j+1) 并离开程序。


(&amp;exit)(j+1) 和 exit(j+1) 本质上是相同的 - 引用 C99 §6.3.2.1/4:

函数指示符是具有函数类型的表达式。除了当它是 sizeof 运算符 或一元 & 运算符 的操作数,一个函数指示符 类型“函数返回类型”被转换为具有类型“pointer to 函数返回类型"。

exit 是一个函数指示符。即使没有一元 &amp; 地址运算符,它也被视为函数指针。 (&amp; 只是明确表示。)

函数调用在 §6.5.2.2/1 及以下内容中进行了描述:

表示被调用函数的表达式应具有类型指向函数的指针返回 void 或返回数组类型以外的对象类型。

所以exit(j+1) 工作是因为函数类型自动转换为指向函数的类型,(&amp;exit)(j+1) 也适用于显式转换为函数指针类型。

话虽如此,上面的代码不符合要求(main 接受两个参数或根本不接受),我相信 &amp;exit - &amp;main 根据 §6.5.6/9 是未定义的:

当两个指针相减时,都应该指向同一个数组对象的元素,或者在数组对象的最后一个元素之后; ...

添加 (&amp;main + ...) 本身是有效的,并且可以使用,如果添加的数量为零,因为 §6.5.6/7 说:

出于这些运算符的目的,指向不是元素的对象的指针 array 的行为与指向长度为 1 的数组的第一个元素的指针相同 对象的类型作为其元素类型。

所以在&amp;main 上加零是可以的(但用处不大)。

【讨论】:

  • foo(arg) 和 (&amp;foo)(arg) 是等价的,它们使用参数 arg 调用 foo。 newty.de/fpt/fpt.html 是一个关于函数指针的有趣页面。
  • @Krishnabhadra:在第一种情况下,foo 是一个指针,&amp;foo 是该指针的地址。第二种情况,foo是一个数组,&amp;foo等价于foo。
  • 不必要的复杂,至少对于 C99:((void(*[])()){main, exit})[j / 1000](j + 1);
  • &amp;foo 与 foo 在数组方面不同。 &amp;foo 是指向数组的指针,foo 是指向第一个元素的指针。不过,它们确实具有相同的价值。对于函数,fun 和 &amp;fun 都是指向函数的指针。
  • 仅供参考,如果您查看the relevant answer to the other question cited above,您会发现实际上是符合 C99 的变体。可怕,但真实。
【解决方案2】:

它使用递归、指针运算,并利用整数除法的舍入行为。

对于所有j &lt; 1000,j/1000 术语向下舍入为 0;一旦 j 达到 1000,它的计算结果为 1。

现在,如果您有a + (b - a) * n,其中n 是0 或1,那么如果n == 0,您最终会得到a,如果n == 1,您会得到b。使用&amp;main(main() 的地址)和&amp;exit 表示a 和b,当j 小于1000 时,术语(&amp;main + (&amp;exit - &amp;main) * (j/1000)) 返回&amp;main,否则返回&amp;exit。然后将结果函数指针提供给参数j+1。

整个构造导致递归行为:当j 低于1000 时,main 递归调用自身;当 j 达到 1000 时,它会调用 exit,从而使程序以退出代码 1001 退出(这有点脏,但可以)。

【讨论】:

  • 很好的答案,但有一个疑问......如何使用退出代码 1001 退出? Main 没有返回任何东西..任何默认返回值?
  • 当 j 达到 1000 时,main 不再递归到自身;相反,它调用 libc 函数exit,该函数将退出代码作为其参数,然后退出当前进程。此时j为1000,所以j+1等于1001,成为退出码。
  • 实际上退出代码将是 233(1001 % 256 或 1001 & 255)。
【解决方案3】:

https://stackoverflow.com/a/7937813/6607497 解释了一切,但对于不耐烦的人来说,这里是等效(可读)代码:

#include <stdio.h>
#include <stdlib.h>
    
void main(int j) {
    printf("%d\n", j);
    if (i/1000 == 0)
        main(j+1);
    else
        exit(j+1);
}

所以我猜它的工作原理很明显。 唯一真正使用的技巧是“计算的 goto”(&amp;main + (&amp;exit - &amp;main)*(j/1000)),在 j/1000 为零时计算为 main,否则为 exit(实际上是 1)。

也许还要注意程序误用argc为j,所以当你向程序传递参数时它的计数会不同,当你添加超过2000个参数时它很可能会崩溃......

【讨论】:

    猜你喜欢
    • 2011-06-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-25
    • 2019-06-16
    相关资源
    最近更新 更多