加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

函数返回值的存放地址

(2014-05-30 22:21:13)
标签:

返回值

eax

rax

struct

it

分类: C
from http://nxlhero.blog.51cto.com/962631/703953

在操作系统中(以linux为例),每个程序都需要有一个返回值,返回给操作系统.

在shell中,可以利用echo $?查看程序的返回值

比如:

$ ls not_exist

ls: not_exist: No such file or directory

$ echo $?

2

$ ls main.c

main.c

$ echo $?

0

可以看到,not_exist不存在,返回2,main.c存在,返回0,一般返回0表示成功,而返回非0表示失败或者其他意义。

其实这个返回值是存放在eax中的,c规范要求main必须返回int,而int和eax长度是一致的(32位系统)。

$ cat ret_test.s

.globl main

main:

       pushq %rbp

       movl $4,�x

       popq %rbp

       ret

$ make ret_test

cc ret_test.s -o ret_test

$ ./ret_test

$ echo $

4

这个汇编程序只有一条指令,将4存到eax,检测返回值发现是4。

如果你的程序用void main(),有的编译器会报错,有的会警告,如果编译过了,运行时一般没问题。

$ cat test.c

int f()

{

return 100;

}

void main()

{

f();

}

$ make test
cc     test.c   -o test
$ ./test
$ echo $?
100

函数f把返回值放到eax了,main函数什么都没做,所以返回值还是100。

但是我们来看另外一个例子cat haha.c 

struct xxx{

int a[50];

};

struct xxx main()

{

struct xxx haha;

return haha;

}


$make haha
cc     haha.c   -o haha
$ ./haha
Segmentation fault (core dumped)
$ echo $?
139

为什么会出现段错误?我们后面会研究它。

 我们先把返回值进行分类

首先是基本类型,void,char,short,long,long long,float,double,指针

然后是结构类型struct。

对于void类型,没有返回值,不做讨论。

char只有1个字节,eax有4个字节,怎么存?只用低8位al就可以了。下面是示例


  1. //示例1:返回值为char  
  2.  
  3. char f()
    {
            char a = 'a';
            return a;
    }
    int main()
    {
            char b = f();
            return 0;
    }
     
  4.  
  5. .file "char.c"
  6. .text
  7. .globl f
  8. f:
  9. pushl �p
  10. movl %esp, �p
  11. subl $16, %esp
  12. movb $97, -1(�p)  //我的显示的为 movb $0x61,-0x1(%rbp) with objdump or the same with gcc -S
  13. movsbl -1(�p),�x //符号扩展,我自个的显示为movzbl -0x1(%rbp),�x
  14. leave
  15. ret
  16. .globl main
  17. main:
  18. leal 4(%esp), �x
  19. andl $-16, %esp  
  20. pushl -4(�x)
  21. pushl �p
  22. movl %esp, �p
  23. pushl �x
  24. subl $16, %esp // sub $0x10,%rsp or subq $16,%rsp
  25. call f
  26. movb %al, -5(�p) //movb %al, -1(%rbp)
  27. movl $0, �x
  28. addl $16, %esp
  29. popl �x
  30. popl �p
  31. leal -4(�x), %esp
  32. ret

从汇编代码中可以看出,调用完f后,main函数从al中找返回值

同样,对于short,int,分别把返回值存放到ax,eax,假如在64位系统里,那么long long 返回值是存到rax的,它的长度为64位,在32位系统里是怎么存的呢?

在32位系统里返回64位数,是通过edx和eax联合实现的,edx存高32位,eax存低32位。



  1.  
  2.  
  3. long long f()  
  4. {  
  5.         long long 5;  
  6.         return a;  
  7. }  
  8. int main()  
  9. {  
  10.         long long b;  
  11.          b=f();  
  12.         return 0;  
  13. }  
  14.  
  15.        .file   "longint.c" 
  16.         .text  
  17. .globl f  
  18. f:  
  19.         pushl   �p  
  20.         movl    %esp, �p  
  21.         subl    $16, %esp  
  22.         movl    $5, -8(�p)  
  23.         movl    $0, -4(�p)  
  24.         movl    -8(�p), �x  
  25.         movl    -4(�p), �x  
  26.         leave  
  27.         ret  
  28. .globl main  
  29. main:  
  30.         leal    4(%esp), �x  
  31.         andl    $-16, %esp  
  32.         pushl   -4(�x)  
  33.         pushl   �p  
  34.         movl    %esp, �p  
  35.         pushl   �x  
  36.         subl    $20, %esp  
  37.         call    f  
  38.         movl    �x, -16(�p)  
  39.         movl    �x, -12(�p)  
  40.         movl    $0, �x  
  41.         addl    $20, %esp  
  42.         popl    �x  
  43.         popl    �p  
  44.         leal    -4(�x), %esp  
  45.         ret  

对于64位的机器来说:

f函数中 movl -8(�p),�x movl -4(�p),�x 变成了mov -0x8(%rbp),%rax

而main函数取返回值的 movl �x,-16(�p) movl �x,-12(�p) 变成了mov %rax -0x8(%rbp)


对于浮点类型,虽然运算过程中会存放在eax等普通寄存器中,但是作为返回值时,不会用eax,edx等,即使运算结果已经存到了eax中,也要再压到浮点数寄存器堆栈中,在主调函数中,会认为返回结果存到浮点数寄存器了,当然,如果你要手动优化汇编代码也是没问题的。

下面是示例。


  1.  
  2. float f()  
  3. {  
  4.         return 0.1;  
  5. }  
  6. int main()  
  7. {  
  8.         float a f();  
  9.         return 0;  
  10. }  
  11.  
  12.         .file   "float.c"  
  13.         .text  
  14. .globl f  
  15. f:  
  16.         pushl   �p  
  17.         movl    %esp, �p  
  18.         subl    $4, %esp  
  19.         movl    $0x3dcccccd, �x  
  20.         movl    �x, -4(�p)  
  21.         flds    -4(�p)  //把结果压到浮点寄存器栈顶  //我的使用的是movss -0x4(%rbp),%xmm0
  22.         leave  
  23.         ret  
  24. .globl main  
  25. main:  
  26.         leal    4(%esp), �x  
  27.         andl    $-16, %esp  
  28.         pushl   -4(�x)  
  29.         pushl   �p  
  30.         movl    %esp, �p  
  31.         pushl   �x  
  32.         subl    $16, %esp  
  33.         call    f  
  34.         fstps   -8(�p) //从浮点寄存器栈顶取数  //我的使用的是movss %xmm0,-0x4(%rbp)
  35.         movl    $0, �x  
  36.         addl    $16, %esp  
  37.         popl    �x  
  38.         popl    �p  
  39.         leal    -4(�x), %esp  
  40.         ret 

关于浮点寄存器及浮点运算指令,可参考:http://www.diybl.com/course/3_program/hb/hbjs/2007124/89946.html

如果返回值为指针?那肯定是用eax(32bit)或者rax(64bit)了。不管是什么类型的指针,都一样,我们来看一个奇怪的程序。


  1.  
  2.  
  3. int f()  
  4. {  
  5.         return 5;  
  6. }  
  7. int (*whatisthis()) ()  //这个函数的返回类型是函数指针
  8. {  
  9.         return f;  
  10. }  
  11. int main()  
  12. {  
  13.         int (*a) ();  
  14.         int b;  
  15.         whatisthis();  
  16.         a();  
  17.         printf("%d\n",b);  
  18.         return 0;  
  19. }  
  20.  
  21.         .file   "ret_fun.c" 
  22.         .text  
  23. .globl f  
  24. f:  
  25.         pushl   �p  
  26.         movl    %esp, �p  
  27.         movl    $5, �x  
  28.         popl    �p  
  29.         ret  
  30.  
  31. .globl whatisthis  
  32. whatisthis:  
  33.         pushl   �p  
  34.         movl    %esp, �p  
  35.         movl    $f, �x  
  36.         popl    �p  
  37.         ret  
  38.  
  39. .LC0:  
  40.         .string "%d\n" 
  41.         .text  
  42.  
  43. .globl main  
  44. main:  
  45.         leal    4(%esp), �x  
  46.         andl    $-16, %esp  
  47.         pushl   -4(�x)  
  48.         pushl   �p  
  49.         movl    %esp, �p  
  50.         pushl   �x  
  51.         subl    $36, %esp  
  52.         call    whatisthis  
  53.         movl    �x, -12(�p)   //我的显示为movq %rax,-16(%rbp)
  54.         movl    -12(�p), �x  //我的显示movq -16(%rbp),%rdx
  55.         call    *�x            //我的显示 call *�x
  56.         movl    �x, -8(�p)   //把返回结果5 放入�x,从而main函数从�x读取返回值5到-8(%rbp)
  57.         movl    -8(�p), �x  
  58.         movl    �x, 4(%esp)  
  59.         movl    $.LC0, (%esp)  
  60.         call    printf  
  61.         movl    $0, �x  
  62.         addl    $36, %esp  
  63.         popl    �x  
  64.         popl    �p  
  65.         leal    -4(�x), %esp  
  66.         ret  

一个函数的返回值可以是函数指针,定义一个这样的函数如下:

函数1   int f(int,char)

函数2   返回值为上面函数的类型的指针,假如函数名为g,参数为float

那么g的定义为     int   (* g(float x)     (int,char)

基本类型讨论完了,那么struct类型呢?struct可大可小,怎么存到寄存器里呢?

答案是:主调函数会把被赋值对象的地址传给被调用函数。你可能会说这不是传引用吗,其实传引用传值什么的都是浮云。

还有一个问题就是,对于struct xxx { char a; };这样的结构也要传地址吗?答案是肯定的,gcc是这样做的,其它编译器可能不这样,当然也可以手动修改汇编代码。


  1.  
  2.  
  3. struct xxx{  
  4.         char a;  
  5. };  
  6. struct xxx  f()  
  7. {  
  8.         struct xxx x;  
  9.         x.a '9';  
  10.         return x;  
  11. }  
  12. int main()  
  13. {  
  14.         struct xxx f();  
  15.         return 0;  
  16. }  
  17.  
  18.         .file   "struct_char.c" 
  19.         .text  
  20. .globl f  
  21. f:  
  22.         pushl   �p  
  23.         movl    %esp, �p  
  24.         subl    $16, %esp  
  25.         movl    8(�p), �x //取出地址,放入edx  
  26.         movb    $57, -1(�p)     // movb $0x39, -0x1(%rbp)
  27.         movzbl  -1(�p), �x //'9'放到 al  
  28.         movb    %al, (�x) //将al内容写到edx指向的地址 我的无此步骤
  29.         movl    �x, �x  
  30.         leave  
  31.         ret     $4  
  32.  
  33. .globl main  
  34. main:  
  35.         leal    4(%esp), �x  
  36.         andl    $-16, %esp  
  37.         pushl   -4(�x)  
  38.         pushl   �p  
  39.         movl    %esp, �p  
  40.         pushl   �x  
  41.         subl    $24, %esp  
  42.         leal    -21(�p), �x //地址放到eax  
  43.         movl    �x, (%esp) //地址压入栈中  
  44.         call      
  45.         subl    $4, %esp    //没有取返回值的指令了  
  46.         movzbl  -21(�p), �x//因为已经写到目的地址了  我的main中使用mov %al, -0x1(%rbp)来取得地址
  47.         movb    %al, -5(�p)  
  48.         movl    $0, �x  
  49.         movl    -4(�p), �x  
  50.         leave  
  51.         leal    -4(�x), %esp  
  52.         ret  

我们再来看个复杂点的例子


  1.  
  2.  
  3. struct xxx {  
  4.         char a[10];  
  5. };  
  6. struct xxx f(int a)  
  7. {  
  8.         struct xxx t;  
  9.         t.a[9] 1;  
  10.         return t;  
  11. }  
  12. int main()  
  13. {  
  14.         struct xxx m=f(1);  
  15.         return 0;  
  16. }  
  17.  
  18.         .file   "struct.c" 
  19.         .text  
  20. .globl f  
  21. f:  
  22.         pushl   �p  
  23.         movl    %esp, �p  
  24.         subl    $16, %esp  
  25.         movl    8(�p), �x   //取地址 我的显示的是是movl �i, -36(%rbp) 读取传入参数,并复制到栈内
  26.         movb    $1, -1(�p)  
  27.         movl    -10(�p), �x  
  28.         movl    �x, (�x)  
  29.         movl    -6(�p), �x  
  30.         movl    �x, 4(�x)  
  31.         movzwl  -2(�p), �x  
  32.         movw    %ax, 8(�x)  
  33.         movl    �x, �x  
  34.         leave  
  35.         ret     $4  
  36.  
  37. .globl main  
  38. main:  
  39.         leal    4(%esp), �x  
  40.         andl    $-16, %esp  
  41.         pushl   -4(�x)  
  42.         pushl   �p  
  43.         movl    %esp, �p  
  44.         pushl   �x  
  45.         subl    $24, %esp  
  46.         leal    -14(�p), �x  
  47.         movl    $1, 4(%esp)      //先压入参数  我的显示 movl $1,�i
  48.         movl    �x, (%esp)     //再压入返回值地址  我的显示没有此项 
  49.         call    f  
  50.         subl    $4, %esp  
  51.         movl    $0, �x  
  52.         movl    -4(�p), �x  
  53.         leave  
  54.         leal    -4(�x), %esp  
  55.         ret  

进入被调用函数后的堆栈情况

参数2

参数1

返回值地址

旧EIP

旧EBP

它会到假定8(�p)处存放着返回值的地址。这也是为什么main的返回值为struct时会引起段错误,main函数认为这个地方存着返回值的地址,实际上这个地方是操作系统写入的特定值,把这个当作返回值的地址乱写,肯定会引起段错误。

下面这个程序

假如对于struct,有返回值的函数却不赋值怎么办?

比如


  1. struct xxx {  
  2.         char a[10];  
  3. };  
  4. struct xxx f(int a)  
  5. {  
  6.         struct xxx t;  
  7.         t.a[9] 1;  
  8.         return t;  
  9. }  
  10. int main()  
  11. {  
  12.         f(1);  
  13.         return 0;  
  14. }  

对于上述程序,主调用函数需要开辟垃圾空间作为返回值空间,感兴趣的可以验证下看看。

如下贴出的是我机器上上述代码的汇编部分

f:

.LFB0:

        .cfi_startproc

        pushq   %rbp

        .cfi_def_cfa_offset 16

        .cfi_offset 6, -16 

        movq    %rsp, %rbp

        .cfi_def_cfa_register 6

        movl    �i, -36(%rbp)

        movb    $1, -23(%rbp)

        movq    -32(%rbp), %rax

        movq    %rax, -16(%rbp)

        movzwl  -24(%rbp), �x

        movw    %ax, -8(%rbp)

        movq    -16(%rbp), %rax //f函数依旧返回地址给%rax

        movzwl  -8(%rbp), �x

        popq    %rbp

        .cfi_def_cfa 7, 8

        ret 

        .cfi_endproc

.LFE0:

        .size   f, .-f 

        .globl  main

        .type   main, @function

main:

.LFB1:

        .cfi_startproc

        pushq   %rbp

        .cfi_def_cfa_offset 16

        .cfi_offset 6, -16 

        movq    %rsp, %rbp

        .cfi_def_cfa_register 6

        movl    $1, �i

        call    f   

        movl    $0, �x //main 函数对于抛弃的返回值不做处理而直接将�x清零。

        popq    %rbp

        .cfi_def_cfa 7, 8

        ret 

        .cfi_endproc

.LFE1:

        .size   main, .-main

        .ident  "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3"

        .section        .note.GNU-stack,"",@progbits


补充:

gcc支持代码块有返回值

比如a = { int b = 2; int c = 3; c-b;} 最终a = 1;

根据我的测试:代码块里必须有除了变量声明的其他语句,否则不对,不能有return;

另外,只能对基本类型赋值,struct类型不能赋值。

最后的结果是:代码块执行结束后,取出eax的值,检查要赋值的变量类型,如果是char,取al,如果是int,取eax,如果是long long,符号扩展,如果是float或者double,将eax强制转换成浮点数。

下面代码可正常运行:


  1. int main()  
  2. {  
  3.         int a;  
  4.         long long a1;  
  5.         double a2;  
  6.          {int 5; printf("xxx\n");;};  
  7.         a1  {int 5;int 2; 3-4;b-c;};  
  8.         a2  {int 5;int 2; 10-8;};  
  9.         printf("%d\n",a);  
  10.         printf("%ld\n",a1);  
  11.         printf("%lf\n",a2);  
  12.         return 0;  
  13. }  

这个有点商榷,目前我在本机上无法编译成功。三行的block赋值语句都报错:

retblock.c:9:4: error: expected expression before ‘{’ token

retblock.c:10:5: error: expected expression before ‘{’ token

 

retblock.c:11:5: error: expected expression before ‘{’ token

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有