代码是如何工作的?

时间:2011-02-21 08:31:56

标签: c obfuscation undefined-behavior

以下代码在C中。当给出一组输入数字时,它会跳过第一个数字,然后打印其余数字。

main(i)  
{
    if(~scanf("%d",gets(&i)))
        printf("%d\n",i),main();
}

我想知道,这段代码是如何工作的?

  编辑:对于那些想到它的人   不起作用   http://www.ideone.com/cENzy

2 个答案:

答案 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的类型为intgets期望收到它可以写入的char *缓冲区的地址,因此这将破坏内存。