![](/img/trans.png)
[英]How does the C code that prints from 1 to 1000 without loops or conditional statements work?
[英]How does this code that prints 1 to 1000 work in C?
我见过一个C程序,它在不使用任何循环结构或条件语句的情况下可以打印1到1000,但是我不知道它是如何工作的。 任何人都可以遍历代码并解释每一行吗? 在这里观看现场演示。 为什么会导致运行时错误?
#include <stdio.h>
#include <stdlib.h>
int main(int i)
{
printf("%d\n",i);
((void(*[])()){main, exit})[i / 1000](i + 1);
return 0;
}
((void(*[])()){main, exit})[i / 1000](i + 1);
这行代码创建一个由两个元素组成的函数指针数组,第一个元素包含main
函数,另一个元素包含exit
函数。
然后,它以i / 1000
索引此数组。 如果i < 1000
则获取main
函数,或者当i == 1000
时, exit
函数。
然后,它调用刚刚以i+1
作为参数索引的函数指针。
这是一个递归函数,停止条件由数组索引而不是条件索引确定。 但是,我认为这不是有效的C。 main
的签名是错误的,并且强制转换为函数指针会从main
函数中删除返回类型。
打破您要求的界限
((void(*[])()){main, exit})
这是一个数组文字,它创建两个函数指针的未命名临时数组,并将这些指针初始化为指向main
并exit
[i / 1000]
这将索引该临时数组。 整数除法会向0截断,因此当0 <= i <1000时,它将获得元素0(指向main
的指针),而当1000 <= i <1999时,它将获得元素1(指向exit
的指针)
(i + 1);
这将以i + 1作为参数调用指向函数。
这里有一堆未定义的行为。 根据标准,将main
声明为具有单个int参数是非法的,尽管通常可以将命令行参数的数量作为单个参数来工作。 获取指向main
的指针同样是未定义的,因为main
是一个特殊的函数,可能具有非标准的调用约定。
这段代码很好地说明了可以弯曲C而不破坏它的程度。 在编写可理解的 C语言之前,您不应该尝试理解这一点。
以下是主要功能和假设:
首先,假设调用该程序时不带参数。 参数i
通常位于argc
,因此它将具有初始值1(参数数组中的元素数,通常称为argv
)。
接下来,假设main
的单参数形式不会引起任何问题。 正式支持的main
形式不带参数,2个参数( argc, argv
),有时还包括3个参数( argc, argv, envp
)。
因此,第一个printf
打印1。
接下来,我们有一个复合文字。 如果您先看一个简单的书,可能会更容易理解:
(int[]){10,20}
这是2个int
的匿名数组,值分别为10和int[]
是类型。 它在括号内的值列表之前。 那是什么
(void(*[])()){main, exit}
void(*[])()
是一种类型。 这意味着带有签名void (*foo)()
的函数指针数组。 括号中的类型名称后跟括号列表是复合文字,就像(int[]){10,20}
示例一样。 在这种情况下,它将创建一个由2个函数指针组成的数组,其元素为main
和exit
。
假设函数指针类型(返回void
)和main
函数(返回int
)之间的不匹配不会导致问题。
这个:
((void(*[])()){main, exit})[i / 1000]
是我们的2个元素的匿名数组,在一些多余的括号内,后跟[i / 1000]
。 那只是普通的数组索引语法。 如果i/1000
为0,则获得数组的第一个元素( thearray[0]
),即main
。 对于所有i
在0到999之间的情况,都会发生这种情况。如果i/1000
为1,则在i==1000
时发生,我们在看thearray[1]
,它为我们提供了第二个元素: exit
。
到目前为止,我们有一个表达式taht在i<1000
时等于main
,在i==1000
时等于exit
。
现在来看整个语句:
...that_big_thing_that_is_either_main_or_exit...(i + 1)
函数指针,后跟带括号的参数列表。 那是一个函数调用。 无论我们从数组中选择哪个函数,现在都将调用它,并提供一个等于传入参数( i
)加1的参数。
因此,当i
为1时,第一个调用为函数选择main
,为参数选择i+1
= 1+1
= 2。 它调用main(2)
。
main(2)
执行printf
打印2,然后调用main(3)
。
main(3)
做一个printf
,打印出3,然后调用main(4)
。
...依此类推,直到...
main(999)
做一个printf
,打印出999,然后调用main(1000)
。
main(1000)
做一个printf
,打印出1000,然后调用exit(1001)
。
对main
任何调用都不会返回,并且return 0
永远不会发生,因为exit
终止该过程。 该进程返回退出代码1001而不是0的事实似乎是ideone的“运行时错误”消息的原因。
main function
是递归调用的。
void print_nb(int n)
{
if (n <= 1000)
{
printf("%d\n", n);
print_nb(n + 1);
}
}
就像Sourav Ghosh所说的那样,禁止与main()
递归使用,最好使用另一个函数。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.