下面的代码是用 C 编写的。当给定一组输入数字时,它会跳过第一个数字,并打印其余数字。
main(i)
{
if(~scanf("%d",gets(&i)))
printf("%d\n",i),main();
}
我想知道,这段代码是如何工作的?
EDIT: For those of you, who think it does not work http://www.ideone.com/cENzy
最佳答案
此代码在 C 中是不合法的。main
必须采用零个、两个或三个参数。一个参数不是合法的选项。 获取
在堆栈上涂鸦。坦率地说,如果这能奏效那就是奇迹了——未定义的行为比比皆是!
话虽如此,让我们看一下在 x86 上编译 C 代码的典型方式,以了解其工作原理。首先,main(i)
是一个旧的 K&R 风格的声明。它被解释为 int main(int i)
,但没有设置真正的原型(prototype) - 因此将来对 main
的调用不会对其参数进行类型检查。回想一下,在 x86 上,参数是通过在调用目标函数之前将它们压入堆栈来传递的。因此,如果我们的参数数量错误,它不会崩溃(假设您使用的是这种 ABI!),而只是提供虚假数据。
另请注意,在 x86 上,堆栈向下增长 - 当您调用函数时,当前堆栈指针减少。这意味着,如果您在这些参数之一之上破坏了内存,您将破坏属于调用函数的内存,并且在您返回之前可能不会注意到。
现在让我们看看执行流程。 gets(&i)
首先执行,并且(假设编译器忽略类型不匹配!)获取一行文本,并将它存储到覆盖调用者堆栈帧的堆栈上! 这里假设一个堆栈在内存中向下增长;在向上增长的堆栈上,这将根据字符串的长度覆盖 gets
的返回地址并可能崩溃。
尽管 gets
抓取了一行文本,但该文本将被忽略并丢弃。这是因为gets
的返回值,即&i
,会被传递给scanf
。所以 scanf
读取一个整数并将其存储在 i
中。没问题。 scanf
然后返回 1
,它是二进制否定的某个负非零值,这是真的,所以 printf
然后打印该值。然后,逗号运算符用于再次递归调用 main,并使用错误数量的参数(参数通常会使用一些虚假值进行初始化),这就像一个循环。
请注意,在 scanf
返回后,换行符保留在输入中未被使用,因此 gets
会在下一次处理它。另请注意,当 EOF 发生时,scanf
将返回 EOF (0xFFFFFFFF),逻辑取反为 0。main
将返回,并迅速崩溃,因为其调用者的堆栈已可能被 gets
覆盖。
总而言之,这是一个巧妙的 hack,但高度依赖于未定义的行为。请不要在真实代码中模仿这一点。
关于c - 代码是如何工作的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5063748/