C语言函数入参和返回值是结构体时的汇编分析

原创 2017年08月13日 13:45:12

在C语言程序中,一般不会直接传一个结构体给一个函数,也不会让函数的返回值直接返回一个结构体,这样会拷贝过多影响效率。但是这样也是合法的,有时候也会使用,并且有时候效率也并不会变得太差。

  • C函数传参:参数少或者传入的结构体小只借助寄存器即可,否则借助栈。
  • C函数返回值:如果返回一个比较小的结构体,借助寄存器即可,否则依旧借助栈。按调用约定,当返回值是较大的结构体时,会在caller栈里产生一个临时变量,并将其首地址传给callee,callee返回值会修改此变量做到将返回值返回给caller。

看一个例子, x64的Linux系统下:

#include <stdio.h>

struct Cord {
    int x;
    int y;
};

struct Cord add(struct Cord b)
{
    b.x++;
    b.y++;
    return b;
}

int main()
{
    struct Cord a = {2, 5};
    struct Cord re = add(a);
    printf("re (%d, %d)\n", re.x, re.y);
    return 0;
}

对于上面的代码,我们从汇编角度看一下如何实现结构体做函数入参和返回值的:

(gdb) disass main
Dump of assembler code for function main:
   0x000000000040054d <+0>:     push   %rbp
   0x000000000040054e <+1>:     mov    %rsp,%rbp
   0x0000000000400551 <+4>:     sub    $0x20,%rsp
   0x0000000000400555 <+8>:     movl   $0x2,-0x20(%rbp)
   0x000000000040055c <+15>:    movl   $0x5,-0x1c(%rbp)
   0x0000000000400563 <+22>:    mov    -0x20(%rbp),%rax   //结构体8字节,从首地址拷贝,直接拷贝到 %rax
   0x0000000000400567 <+26>:    mov    %rax,%rdi          //调用约定 %rdi, 可直接把这个8字节的结构体传入
   0x000000000040056a <+29>:    callq  0x40052d <add>     // 打断点 1,看后面分析
   0x000000000040056f <+34>:    mov    %rax,-0x10(%rbp)
   0x0000000000400573 <+38>:    mov    -0xc(%rbp),%edx
   0x0000000000400576 <+41>:    mov    -0x10(%rbp),%eax
   0x0000000000400579 <+44>:    mov    %eax,%esi
   0x000000000040057b <+46>:    mov    $0x400624,%edi
   0x0000000000400580 <+51>:    mov    $0x0,%eax
   0x0000000000400585 <+56>:    callq  0x400410 <printf@plt>
   0x000000000040058a <+61>:    mov    $0x0,%eax   // 断点2
   0x000000000040058f <+66>:    leaveq
   0x0000000000400590 <+67>:    retq
End of assembler dump.
(gdb) disass add
Dump of assembler code for function add:
   0x000000000040052d <+0>:     push   %rbp
   0x000000000040052e <+1>:     mov    %rsp,%rbp
   0x0000000000400531 <+4>:     mov    %rdi,-0x10(%rbp)
   0x0000000000400535 <+8>:     mov    -0x10(%rbp),%eax
   0x0000000000400538 <+11>:    add    $0x1,%eax
   0x000000000040053b <+14>:    mov    %eax,-0x10(%rbp)
   0x000000000040053e <+17>:    mov    -0xc(%rbp),%eax
   0x0000000000400541 <+20>:    add    $0x1,%eax
   0x0000000000400544 <+23>:    mov    %eax,-0xc(%rbp)
   0x0000000000400547 <+26>:    mov    -0x10(%rbp),%rax //返回值 %rax可以直接把这个8字节的结构带出
   0x000000000040054b <+30>:    pop    %rbp             //打断点2,看后面分析
   0x000000000040054c <+31>:    retq
End of assembler dump.
(gdb) r
Starting program: /tmp/a.out
Breakpoint 1, 0x000000000040056a in main ()
(gdb) p $edi //可以看出,这个 %rdi 8字节寄存器正好放的是入参结构体
$3 = 2
(gdb) p $rdi >> 32
$4 = 5
Breakpoint 2, 0x000000000040054b in add ()
(gdb) p $eax
$1 = 3
(gdb) p $rax >> 32
$2 = 6
(gdb)

从上面的例子就很容易看出,C程序是如何用结构体作为入参和返回值的,编译后的汇编指令是没有类型概念的,结构体也就是对一块连续的内存的布局的解释而已,结合x64平台的C calling convention,就很好理解这些内容了。

版权声明:本文为博主原创文章,未经博主允许不得转载。[http://blog.csdn.net/thisinnocence]

相关文章推荐

平台调用P-INVOKE完全掌握, 反汇编细解结构体作为返回值

这篇解决上篇那个结构体作为返回值的问题。我们结合反汇编来探索这里面的秘密。如何反汇编? 方法如下:在C++函数内下断点,调试到断点断下,右键菜单,选择"反汇编",反汇编是VS自带功能。  ...

gsoap_返回值为结构体数组

  • 2016-07-29 10:51
  • 1.25MB
  • 下载

结构体变量作为函数的参数和返回值 .

http://blog.csdn.net/jinn3/article/details/7590082   结构体变量作为函数的参数和返回值 2010-07-28 13:14 ...

结构体变量作为函数的参数和返回值

结构体变量作为函数的参数和返回值 2010-07-28 13:14 准备学习一下OpenCV,可是第一个知识点的语法就没有看懂: typedef struct CvPoint...
  • jinn3
  • jinn3
  • 2012-05-22 10:58
  • 4632

简单C语言反汇编(循环,判断,数组,结构体,共用体,枚举类型)

关键一句:以C语言为母语的程序员,常常 找一些汇编语言改写为C语言,是学习汇编 的快捷方式之一。                      一,for循环 int ...

C语言开发函数库时利用不透明指针对外隐藏结构体细节

1 模块化设计要求库接口隐藏实现细节作为一个函数库来说,尽力减少和其调用方的耦合,是最基本的设计标准。C语言,作为经典“程序=数据结构+算法”的践行者,在实现函数库的时候,必然存在大量的结构体定义,接...

C语言之Main函数返回值问题分析

很多人甚至市面上的一些书籍,都使用了void main( ) ,其实这是错误的。C/C++ 中从来没有定义过void main( ) 。C++ 之父 Bjarne Stroustrup 在他...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)