代码是如何工作的?

fR0*_*DDY 2 c obfuscation undefined-behavior

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

main(i)  
{
    if(~scanf("%d",gets(&i)))
        printf("%d\n",i),main();
}
Run Code Online (Sandbox Code Playgroud)

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

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

bdo*_*lan 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然后将返回,并且因为其调用者的堆栈可能已被覆盖而立即崩溃gets.

总而言之,这是一个整洁的黑客,但高度依赖于未定义的行为.请不要在实际代码中模仿这个.