以下代码在C中。当给出一组输入数字时,它会跳过第一个数字,然后打印其余数字。
main(i)
{
if(~scanf("%d",gets(&i)))
printf("%d\n",i),main();
}
我想知道,这段代码是如何工作的?
编辑:对于那些想到它的人 不起作用 http://www.ideone.com/cENzy
答案 0 :(得分:8)
此代码不合法C. main
必须采用零,两或三个参数。一个参数不是合法选项。 gets
在堆栈上涂鸦。坦率地说,如果这种方法起作用,这是一个奇迹 - 未定义的行为比比皆是!
说到这里,让我们看一下在x86上编译C代码的典型方法,以了解它是如何工作的。首先,main(i)
是旧的K& R风格的声明。它被解释为int main(int i)
,但没有设置真正的原型 - 因此将来调用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
将返回,并立即崩溃,因为其调用者的堆栈可能已被{{覆盖1}}。
总而言之,这是一个整洁的黑客,但高度依赖于未定义的行为。请不要在实际代码中模仿这个。
答案 1 :(得分:0)
此代码的行为未定义。如果没有进一步的限定,i
的类型为int
。 gets
期望收到它可以写入的char *
缓冲区的地址,因此这将破坏内存。