Chinaunix首页 | 论坛 | 博客
  • 博客访问: 530747
  • 博文数量: 118
  • 博客积分: 3995
  • 博客等级: 中校
  • 技术积分: 1276
  • 用 户 组: 普通用户
  • 注册时间: 2005-11-15 12:15
文章分类

全部博文(118)

文章存档

2014年(1)

2013年(1)

2010年(6)

2009年(27)

2008年(10)

2007年(33)

2006年(38)

2005年(2)

我的朋友

分类: LINUX

2006-10-23 23:44:13

下面这个程序的输出为该程序本身
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,中文版为“深入理解计算机系统”。
阅读(1112) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~