打印1到1000的代码如何在C中工作?

时间:2015-11-30 16:10:45

标签: c function-pointers

我见过一个C程序打印1到1000而不使用任何循环结构或条件语句,但我不明白它是如何工作的。任何人都可以通过代码解释每一行吗? 查看实时演示here。为什么会导致运行时错误?

#include <stdio.h>
#include <stdlib.h>
int main(int i)
{
    printf("%d\n",i);
    ((void(*[])()){main, exit})[i / 1000](i + 1);
    return 0;
}

4 个答案:

答案 0 :(得分:4)

var commentedOutHTml = [];
var iterator = document.createNodeIterator(document.body, NodeFilter.SHOW_COMMENT, NodeFilter.FILTER_ACCEPT, false);
var currentNode;
while (currentNode = iterator.nextNode()) {
    commentedOutHTml.push(currentNode.nodeValue);
}

alert(commentedOutHTml.toString());

这一行创建一个两元素的函数指针数组,第一个元素包含<RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content"> ... <TextView android:id="@+id/dateLabel" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentStart="true" android:layout_alignParentLeft="true" android:layout_alignTop="@+id/date" android:layout_alignBottom="@+id/date" android:gravity="center" android:layout_marginEnd="@dimen/labelMargin" android:layout_marginStart="@dimen/labelMargin" android:text="Date*" android:textSize="16sp" android:textColor="#000000"/> <DatePicker android:id="@+id/date" android:layout_width="match_parent" android:layout_height="130dp" android:layout_alignLeft="@+id/name" android:layout_toEndOf="@+id/dateLabel" android:layout_toRightOf="@+id/dateLabel" android:calendarViewShown="false" android:datePickerMode="spinner"> </DatePicker> ... </RelativeLayout> 函数,另一个元素包含((void(*[])()){main, exit})[i / 1000](i + 1); 函数。

然后它按main索引此数组。如果exit获得i / 1000功能,main时获得i < 1000功能。

然后调用刚刚用exit作为参数索引的函数指针。

这是一个递归函数,使用数组索引而不是条件确定停止条件。我不认为这是有效的C; i == 1000的签名是错误的,并且对函数指针的强制转换会从i+1函数中删除返回类型。

答案 1 :(得分:4)

打破你提出的关于

的界限
((void(*[])()){main, exit})

这是一个数组文字,它创建一个包含两个函数指针的未命名临时数组,并将这些指针初始化为指向mainexit

[i / 1000]

这是对temp数组的索引。整数除法向0截断,因此当0 <= i <0时1000,这得到元素0(指向main的指针)并且当1000&lt; = i&lt; 1999年,它获得了元素1(指向exit

的指针
(i + 1);

这用i + 1作为参数调用指向的函数。

这里有很多东西都是未定义的行为。根据标准将main声明为具有单个int参数是非法的,但通常会起作用,将命令行参数的数量作为单个参数。获取指向main的指针同样未定义,因为main是一个可能具有非标准调用约定的特殊函数。

答案 2 :(得分:3)

这段代码是一个很好的例子,说明你可以弯曲C而不会破坏它。在您可以编写可理解的 C。

之前,您不应该尝试理解这一点

以下是主要特征和假设:

首先,假设程序是在没有参数的情况下调用的。参数i位于通常称为argc的位置,因此它将具有初始值1(参数数组中的元素数,通常称为argv)。

接下来,假设main的单参数形式不会导致任何问题。官方支持的main形式没有参数,2个参数(argc, argv),有时3个参数(argc, argv, envp)。

所以第一个printf打印1。

接下来我们有一个复合文字。如果你先看一个更简单的一个,可能会更容易理解:

(int[]){10,20}

这是一个2 int s的匿名数组,值为10和20. int[]是类型。它在括号列表中放在括号中。那是什么:

(void(*[])()){main, exit}

void(*[])()是一种类型。它表示带有签名void (*foo)()的函数指针数组。括号中的类型名称后跟一个支撑列表是复合文字,就像(int[]){10,20}示例一样。在这种情况下,它会创建一个包含2个函数指针的数组,其元素为mainexit

假设函数指针类型(返回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

到目前为止,我们在main时的表达式等于i<1000,在exit时等于i==1000

现在看看整个声明:

...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)执行打印2的printf,然后调用main(3)

main(3)执行打印3的printf,然后调用main(4)

...依此类推......

main(999)执行打印999的printf,然后调用main(1000)

main(1000)执行打印1000的printf,然后调用exit(1001)

main的所有调用都没有返回,return 0从未发生,因为exit终止了该过程。事件返回退出代码1001而不是0的事实似乎是ideone的“运行时错误”消息的原因。

答案 3 :(得分:0)

递归调用main function

void print_nb(int n)
{
    if (n <= 1000)
    {
        printf("%d\n", n);
        print_nb(n + 1);
    }
}

像Sourav Ghosh所说的那样,禁止以递归方式使用main(),更好地使用其他功能。