这段打印 1 到 1000 的代码在 C 中如何工作?
How does this code that prints 1 to 1000 work in C?
我见过一个 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;
}
((void(*[])()){main, exit})[i / 1000](i + 1);
此行创建一个函数指针的二元数组,第一个元素包含 main
函数,另一个元素包含 exit
函数。
然后它通过 i / 1000
索引这个数组。这要么在 i < 1000
时获得 main
函数,要么在 i == 1000
.
时获得 exit
函数
然后它调用它刚刚用 i+1
作为参数索引的函数指针。
这是一个递归函数,停止条件由数组索引而不是条件确定。但是我认为它不是有效的 C; main
的签名错误,转换为函数指针会从 main
函数中删除 return 类型。
分解您询问的行
((void(*[])()){main, exit})
这是一个数组文字,它创建一个包含两个函数指针的未命名临时数组,并将这些指针初始化为指向 main
和 exit
[i / 1000]
这会索引该临时数组。整数除法向 0 截断,因此当 0 <= i < 1000 时,它获取元素 0(指向 main
的指针),当 1000 <= i < 1999 时,它获取元素 1(指向 [=14= 的指针) ])
(i + 1);
这将以 i+1 作为参数调用指向的函数。
这里有很多东西是未定义的行为。根据标准,将 main
声明为具有单个 int 参数是非法的,但通常会起作用,将命令行参数的数量作为单个参数。获取指向 main
的指针同样是未定义的,因为 main
是一个可能具有非标准调用约定的特殊函数。
递归调用main function
。
void print_nb(int n)
{
if (n <= 1000)
{
printf("%d\n", n);
print_nb(n + 1);
}
}
正如 Sourav Ghosh 所说,禁止使用 with main()
递归,最好使用其他函数。
这段代码很好地说明了在不破坏 C 的情况下可以将其弯曲多远。在你可以写 understandable C.
之前,你不应该试图理解这一点
以下是主要特征和假设:
首先,假设程序是在没有参数的情况下调用的。参数i
在通常称为argc
的地方,因此它的初始值为1(参数数组中元素的个数,通常称为argv
)。
接下来,假设 main
的单参数形式不会导致任何问题。 main
的官方支持形式是没有参数、2 个参数 (argc, argv
),有时是 3 个参数 (argc, argv, envp
)。
所以第一个 printf
打印 1.
接下来我们有一个复合文字。先看一个比较简单的可能会更容易理解:
(int[]){10,20}
这是一个 2 int
的匿名数组,值为 10 和 20。int[]
是类型。它放在括号值列表之前的括号中。那么这是什么:
(void(*[])()){main, exit}
void(*[])()
是一种类型。它表示具有签名 void (*foo)()
的函数指针数组。括号中的类型名称后跟花括号列表是复合文字,就像 (int[]){10,20}
示例一样。在本例中,它创建了一个包含 2 个函数指针的数组,其元素为 main
和 exit
.
假设函数指针类型 (returns void
) 和 main
(returns int
) 不匹配不会导致一个问题。
这个:
((void(*[])()){main, exit})[i / 1000]
是我们的 2 个元素的匿名数组,在一些多余的括号内,后跟 [i / 1000]
。这只是普通的数组索引语法。如果 i/1000
为 0,则得到数组的第一个元素 (thearray[0]
),即 main
。对于所有介于 0 和 999 之间的 i
,都会发生这种情况。如果 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)
.
None 调用 main
曾经 return,而 return 0
从未发生过,因为 exit
终止了进程。进程 return 的退出代码为 1001 而不是 0 的事实似乎是 ideone 的 "runtime error" 消息的原因。
我见过一个 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;
}
((void(*[])()){main, exit})[i / 1000](i + 1);
此行创建一个函数指针的二元数组,第一个元素包含 main
函数,另一个元素包含 exit
函数。
然后它通过 i / 1000
索引这个数组。这要么在 i < 1000
时获得 main
函数,要么在 i == 1000
.
exit
函数
然后它调用它刚刚用 i+1
作为参数索引的函数指针。
这是一个递归函数,停止条件由数组索引而不是条件确定。但是我认为它不是有效的 C; main
的签名错误,转换为函数指针会从 main
函数中删除 return 类型。
分解您询问的行
((void(*[])()){main, exit})
这是一个数组文字,它创建一个包含两个函数指针的未命名临时数组,并将这些指针初始化为指向 main
和 exit
[i / 1000]
这会索引该临时数组。整数除法向 0 截断,因此当 0 <= i < 1000 时,它获取元素 0(指向 main
的指针),当 1000 <= i < 1999 时,它获取元素 1(指向 [=14= 的指针) ])
(i + 1);
这将以 i+1 作为参数调用指向的函数。
这里有很多东西是未定义的行为。根据标准,将 main
声明为具有单个 int 参数是非法的,但通常会起作用,将命令行参数的数量作为单个参数。获取指向 main
的指针同样是未定义的,因为 main
是一个可能具有非标准调用约定的特殊函数。
递归调用main function
。
void print_nb(int n)
{
if (n <= 1000)
{
printf("%d\n", n);
print_nb(n + 1);
}
}
正如 Sourav Ghosh 所说,禁止使用 with main()
递归,最好使用其他函数。
这段代码很好地说明了在不破坏 C 的情况下可以将其弯曲多远。在你可以写 understandable C.
之前,你不应该试图理解这一点以下是主要特征和假设:
首先,假设程序是在没有参数的情况下调用的。参数i
在通常称为argc
的地方,因此它的初始值为1(参数数组中元素的个数,通常称为argv
)。
接下来,假设 main
的单参数形式不会导致任何问题。 main
的官方支持形式是没有参数、2 个参数 (argc, argv
),有时是 3 个参数 (argc, argv, envp
)。
所以第一个 printf
打印 1.
接下来我们有一个复合文字。先看一个比较简单的可能会更容易理解:
(int[]){10,20}
这是一个 2 int
的匿名数组,值为 10 和 20。int[]
是类型。它放在括号值列表之前的括号中。那么这是什么:
(void(*[])()){main, exit}
void(*[])()
是一种类型。它表示具有签名 void (*foo)()
的函数指针数组。括号中的类型名称后跟花括号列表是复合文字,就像 (int[]){10,20}
示例一样。在本例中,它创建了一个包含 2 个函数指针的数组,其元素为 main
和 exit
.
假设函数指针类型 (returns void
) 和 main
(returns int
) 不匹配不会导致一个问题。
这个:
((void(*[])()){main, exit})[i / 1000]
是我们的 2 个元素的匿名数组,在一些多余的括号内,后跟 [i / 1000]
。这只是普通的数组索引语法。如果 i/1000
为 0,则得到数组的第一个元素 (thearray[0]
),即 main
。对于所有介于 0 和 999 之间的 i
,都会发生这种情况。如果 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)
.
None 调用 main
曾经 return,而 return 0
从未发生过,因为 exit
终止了进程。进程 return 的退出代码为 1001 而不是 0 的事实似乎是 ideone 的 "runtime error" 消息的原因。