没有循环或条件语句的从1到1000打印的C代码如何工作?


148

我发现打印出的C代码从1到1000都没有循环或有条件:但是我不明白它是如何工作的。任何人都可以遍历代码并解释每一行吗?

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

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

1
您是使用C还是C ++进行编译?您看到什么错误?您不能main使用C ++进行调用。
ninjalj 2011年

@ninjalj我创建了一个C ++项目,并复制/粘贴了错误代码:非法,左操作数的类型为'void(
__cdecl

1
@ninjalj这些代码在ideone.org上起作用,但在visual studio ideone.com/MtJ1M上不起作用
ob_dev 2011年

@oussama相似,但略有难以理解:ideone.com/2ItXm 不客气。:)
标记

2
我已经从这些行中删除了所有'&'字符(&main +(&exit-&main)*(j / 1000))(j + 1); 并且此代码仍然有效。
ob_dev 2011年

Answers:


264

永远不要那样写代码。


对于j<1000j/1000为零(整数除法)。所以:

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

等效于:

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

这是:

(&main)(j+1);

这就要求mainj+1

如果为j == 1000,则相同的行显示为:

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

归结为

(&exit)(j+1);

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


(&exit)(j+1)并且exit(j+1)本质上是同一件事-引用C99§6.3.2.1/ 4:

函数指示符是具有函数类型的表达式。除非是sizeof运算符或一元&运算符的操作数,否则类型为“ 函数返回类型 ” 的函数指示符转换为具有类型为“ 函数返回类型的指针 ”的表达式。

exit是一个功能指示符。即使没有一元&地址运算符,它也被视为函数的指针。(这&使得它很明确。)

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

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

exit(j+1)之所以如此,是因为将函数类型自动转换为指向函数的指针类型,并且(&exit)(j+1)也可以将其显式转换为指向函数的指针类型。

话虽如此,上面的代码不符合要求(main要么接受两个参数,要么不接受任何参数),并且&exit - &main我认为根据§6.5.6/ 9仍未定义:

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

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

就这些运算符而言,指向不是数组元素的对象的指针的行为与指向长度为1且对象类型为其元素类型的数组的第一个元素的指针的行为相同。

因此,将零添加到&main是可以的(但使用不多)。


4
foo(arg)并且(&foo)(arg)等效,它们使用参数arg调用foo。newty.de/fpt/fpt.html是有关函数指针的有趣页面。
垫子

1
@Krishnabhadra:在第一种情况下,foo是一个指针,&foo是该指针的地址。在第二种情况下,foo是一个数组,&foo等效于foo。

8
不必要的复杂,至少对于C99:((void(*[])()){main, exit})[j / 1000](j + 1);
Per Johansson

1
&foofoo数组不同。&foo是指向数组foo的指针,是指向第一个元素的指针。它们确实具有相同的价值。对于函数,fun&fun都是函数的指针。
Per Johansson,

1
仅供参考,如果您查看上述其他问题的相关答案,就会发现实际上有一个符合C99的变体。吓人,但是真的。
Daniel Pryden 2011年

41

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

该项j/1000对所有取整至0 j < 1000; 一旦j达到1000,则评估为1。

现在,如果您拥有a + (b - a) * n,其中where n为0或1,则最终得到aif n == 0bif n == 1。使用&main(的地址main())和&exitfor ab,该术语在低于1000 时(&main + (&exit - &main) * (j/1000))返回,否则返回。然后将结果函数指针送入参数。&mainj&exitj+1

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


1
好的答案,但有一个疑问。.如何使用退出代码1001退出主出口?Main没有返回任何内容。是否有任何默认返回值?
Krishnabhadra 2011年

2
当j达到1000时,main不再递归于自身;而是调用libc函数exit,该函数将退出代码作为其参数,然后退出当前进程。此时,j为1000,因此j + 1等于1001,这成为退出代码。
tdammers,2011年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.