下面这个程序的输出为该程序本身
main(a){
printf(a,34,a="main(a){ printf(a,34,a=%c%s%c,34);}",34);
}
咋一看这个程序有语法错误,事实上没有,除了编译会有warning外,(34为双引号“的ascii码)。
main函数没有接收任何参数,a其实是个占位符号,就好像加载函数给main函数传递了参数a(我把它称为伪参数吧)。
先看下面的这个程序:
main(){
tt();
}
tt(a){
a=44;
printf("%d",a);
}
输出:44
子函数tt在被调用的时候没有接收任何参数,该程序转成汇编代码(gcc -S):
────a.s──────
...
main:
pushl %ebp 保存栈帧
movl %esp, %ebp 初始化栈指针
subl $8, %esp 下面8行(到a0)把esp减40,留下一些空闲的栈空间
andl $-16, %esp
movl $0, %eax
addl $15, %eax
addl $15, %eax
shrl $4, %eax
sall $4, %eax
subl %eax, %esp a0
call tt 调用tt子函数
leave main函数返回前的准备,即movl %ebp,%esp和popl %ebp
ret
.size main, .-main
.section .rodata
.LC0:
.string "%d\n"
.text
.globl tt
.type tt, @function
tt:
pushl %ebp 保存栈帧
movl %esp, %ebp 初始化栈指针
subl $8, %esp
movl $44, 8(%ebp) 把44附值给a
subl $8, %esp
pushl 8(%ebp) 把a压栈,作为调用printf的第二个参数
pushl $.LC0 把格式化字符串"%d\n"的地址压栈,作为调用printf的第一个参数
call printf 调用printf函数
addl $16, %esp
leave
ret 子函数tt返回
...
再来比较一下下面的代码
main(){
ttt(44);
}
ttt(int a){
printf("%d",a);
}
子函数ttt接收了一个整型值,汇编代码如下
──────b.s────────
...
main:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
andl $-16, %esp
movl $0, %eax
addl $15, %eax
addl $15, %eax
shrl $4, %eax
sall $4, %eax
subl %eax, %esp 到这行为止都与上前面的例子一样
subl $12, %esp
pushl $44 把44压栈,作为子函数ttt的参数
call ttt
addl $16, %esp
leave
ret
.size main, .-main
.section .rodata
.LC0:
.string "%d"
.text
.globl ttt
.type ttt, @function
ttt:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
subl $8, %esp
pushl 8(%ebp) 把main传来的参数a的值(44)压栈,作为调用printf的第二个参数 pushl $.LC0 把格式化字符串 "%d"的地址压栈,作为调用printf的第一个参数 call printf addl $16, %esp
leave
ret
...
分析两段代码可以发现,它们之间只有微妙的差别(彩色部分),两个程序的栈空间图如下:
可以给多个伪参数,如ttt(a,b,c),每个这种参数占一个机器字,这里4字节,可以使用的伪参数个数要看编译器预留的栈空间大小了,上面调用ttt的main函数预留了40字节,即10个字,也就是说最多可以有10个这种参数。
理解了这两个程序,那么那个quine也很容易理解了。了解相关内容可以看computer system -a progamming's perspective,中文版为“深入理解计算机系统”。
阅读(1149) | 评论(0) | 转发(0) |