在C语言编程中,我们几乎不可能看见有人将一个结构体变量作为参数进行传递,因为效率太低了。本文尝试从反汇编的角度给出其中的缘由。

对于C语言来说,所有的参数传递都是值传递。如果一个变量为指针,那么传递的就是指针变量的值(即某个内存地址)。

那么,如果一个参数是结构体变量(包括多个成员),怎么从caller传递到callee呢?

先看下面的代码片段:

o foo1.c

 #define FALSE 0
#define TRUE (!0) typedef struct point_s {
int x;
int y;
int z;
} point_t; static int cmp(point_t a, point_t b)
{
if (a.x != b.x)
return FALSE;
if (a.y != b.y)
return FALSE;
if (a.z != b.z)
return FALSE;
return TRUE;
} int main(int argc, char *argv[])
{
point_t a = { .x = 0x1, .y = 0x2, .z = +0x3 };
point_t b = { .x = 0x1, .y = 0x2, .z = -0x3 };
return !cmp(a, b);
}

o 对foo1.c进行编译后反汇编

 $ gcc -g -Wall -m32 -std=gnu99 -o foo1 foo1.c
$ gdb foo1
(gdb) set disassembly-flavor intel
(gdb) disas /m main
Dump of assembler code for function main:
{
0x0804842a <+>: push ebp
0x0804842b <+>: mov ebp,esp
0x0804842d <+>: sub esp,0x38 point_t a = { .x = 0x1, .y = 0x2, .z = +0x3 };
0x08048430 <+>: mov DWORD PTR [ebp-0x18],0x1
0x08048437 <+>: mov DWORD PTR [ebp-0x14],0x2
0x0804843e <+>: mov DWORD PTR [ebp-0x10],0x3 point_t b = { .x = 0x1, .y = 0x2, .z = -0x3 };
0x08048445 <+>: mov DWORD PTR [ebp-0xc],0x1
0x0804844c <+>: mov DWORD PTR [ebp-0x8],0x2
0x08048453 <+>: mov DWORD PTR [ebp-0x4],0xfffffffd return !cmp(a, b);
0x0804845a <+>: mov eax,DWORD PTR [ebp-0xc]
0x0804845d <+>: mov DWORD PTR [esp+0xc],eax
0x08048461 <+>: mov eax,DWORD PTR [ebp-0x8]
0x08048464 <+>: mov DWORD PTR [esp+0x10],eax
0x08048468 <+>: mov eax,DWORD PTR [ebp-0x4]
0x0804846b <+>: mov DWORD PTR [esp+0x14],eax
0x0804846f <+>: mov eax,DWORD PTR [ebp-0x18]
0x08048472 <+>: mov DWORD PTR [esp],eax
0x08048475 <+>: mov eax,DWORD PTR [ebp-0x14]
0x08048478 <+>: mov DWORD PTR [esp+0x4],eax
0x0804847c <+>: mov eax,DWORD PTR [ebp-0x10]
0x0804847f <+>: mov DWORD PTR [esp+0x8],eax
0x08048483 <+>: call 0x80483ed <cmp>
0x08048488 <+>: test eax,eax
0x0804848a <+>: sete al
0x0804848d <+>: movzx eax,al }
0x08048490 <+>: leave
0x08048491 <+>: ret End of assembler dump.
(gdb) disas /m cmp
Dump of assembler code for function cmp:
{
0x080483ed <+>: push ebp
0x080483ee <+>: mov ebp,esp if (a.x != b.x)
0x080483f0 <+>: mov edx,DWORD PTR [ebp+0x8]
0x080483f3 <+>: mov eax,DWORD PTR [ebp+0x14]
0x080483f6 <+>: cmp edx,eax
0x080483f8 <+>: je 0x8048401 <cmp+> return FALSE;
0x080483fa <+>: mov eax,0x0
0x080483ff <+>: jmp 0x8048428 <cmp+> if (a.y != b.y)
0x08048401 <+>: mov edx,DWORD PTR [ebp+0xc]
0x08048404 <+>: mov eax,DWORD PTR [ebp+0x18]
0x08048407 <+>: cmp edx,eax
0x08048409 <+>: je 0x8048412 <cmp+> return FALSE;
0x0804840b <+>: mov eax,0x0
0x08048410 <+>: jmp 0x8048428 <cmp+> if (a.z != b.z)
0x08048412 <+>: mov edx,DWORD PTR [ebp+0x10]
0x08048415 <+>: mov eax,DWORD PTR [ebp+0x1c]
0x08048418 <+>: cmp edx,eax
0x0804841a <+>: je 0x8048423 <cmp+> return FALSE;
0x0804841c <+>: mov eax,0x0
0x08048421 <+>: jmp 0x8048428 <cmp+> return TRUE;
0x08048423 <+>: mov eax,0x1 }
0x08048428 <+>: pop ebp
0x08048429 <+>: ret End of assembler dump.
(gdb)

o caller: point_t b的所有成员x, y, z和point_t a的所有成员x, y, z被依次存入到stack上

              point_t a = { .x = 0x1, .y = 0x2, .z = +0x3 };
0x08048430 <+>: mov DWORD PTR [ebp-0x18],0x1 ; a.x = 0x1
0x08048437 <+>: mov DWORD PTR [ebp-0x14],0x2 ; a.y = 0x2
0x0804843e <+>: mov DWORD PTR [ebp-0x10],0x3 ; a.z = +0x3 point_t b = { .x = 0x1, .y = 0x2, .z = -0x3 };
0x08048445 <+>: mov DWORD PTR [ebp-0xc],0x1 ; b.x = 0x1
0x0804844c <+>: mov DWORD PTR [ebp-0x8],0x2 ; b.y = 0x2
0x08048453 <+>: mov DWORD PTR [ebp-0x4],0xfffffffd ; b.z = -0x3 return !cmp(a, b);
0x0804845a <+>: mov eax,DWORD PTR [ebp-0xc] ;
0x0804845d <+>: mov DWORD PTR [esp+0xc],eax ; save b.x to stack
0x08048461 <+>: mov eax,DWORD PTR [ebp-0x8] ;
0x08048464 <+>: mov DWORD PTR [esp+0x10],eax ; save b.y to stack
0x08048468 <+>: mov eax,DWORD PTR [ebp-0x4] ;
0x0804846b <+>: mov DWORD PTR [esp+0x14],eax ; save b.z to stack
0x0804846f <+>: mov eax,DWORD PTR [ebp-0x18] ;
0x08048472 <+>: mov DWORD PTR [esp],eax ; save a.x to stack
0x08048475 <+>: mov eax,DWORD PTR [ebp-0x14] ;
0x08048478 <+>: mov DWORD PTR [esp+0x4],eax ; save a.y to stack
0x0804847c <+>: mov eax,DWORD PTR [ebp-0x10] ;
0x0804847f <+>: mov DWORD PTR [esp+0x8],eax ; save a.z to stack
0x08048483 <+>: call 0x80483ed <cmp> ;

也就是说在caller中调用cmp(a, b)表面上传递了两个实参,其实给stack里压入了6个值。 而对于callee cmp()来说,需要去栈里把对应的6个值取出来使用。

作为对比, 下面的程序片段在cmp()中使用结构体变量指针。

o foo2.c

 #define FALSE 0
#define TRUE (!0) typedef struct point_s {
int x;
int y;
int z;
} point_t; static int cmp(point_t *a, point_t *b)
{
if (a->x != b->x)
return FALSE;
if (a->y != b->y)
return FALSE;
if (a->z != b->z)
return FALSE;
return TRUE;
} int main(int argc, char *argv[])
{
point_t a = { .x = 0x1, .y = 0x2, .z = +0x3 };
point_t b = { .x = 0x1, .y = 0x2, .z = -0x3 };
return !cmp(&a, &b);
}

o foo1.c v.s. foo2.c

o 对foo2.c进行编译后反汇编

 $ gcc -g -Wall -m32 -std=gnu99 -o foo2 foo2.c
$ gdb foo2
(gdb) set disassembly-flavor intel
(gdb) disas /m main
Dump of assembler code for function main:
{
0x0804843a <+>: push ebp
0x0804843b <+>: mov ebp,esp
0x0804843d <+>: sub esp,0x28 point_t a = { .x = 0x1, .y = 0x2, .z = +0x3 };
0x08048440 <+>: mov DWORD PTR [ebp-0x18],0x1
0x08048447 <+>: mov DWORD PTR [ebp-0x14],0x2
0x0804844e <+>: mov DWORD PTR [ebp-0x10],0x3 point_t b = { .x = 0x1, .y = 0x2, .z = -0x3 };
0x08048455 <+>: mov DWORD PTR [ebp-0xc],0x1
0x0804845c <+>: mov DWORD PTR [ebp-0x8],0x2
0x08048463 <+>: mov DWORD PTR [ebp-0x4],0xfffffffd return !cmp(&a, &b);
0x0804846a <+>: lea eax,[ebp-0xc]
0x0804846d <+>: mov DWORD PTR [esp+0x4],eax
0x08048471 <+>: lea eax,[ebp-0x18]
0x08048474 <+>: mov DWORD PTR [esp],eax
0x08048477 <+>: call 0x80483ed <cmp>
0x0804847c <+>: test eax,eax
0x0804847e <+>: sete al
0x08048481 <+>: movzx eax,al }
0x08048484 <+>: leave
0x08048485 <+>: ret End of assembler dump.
(gdb) disas /m cmp
Dump of assembler code for function cmp:
{
0x080483ed <+>: push ebp
0x080483ee <+>: mov ebp,esp if (a->x != b->x)
0x080483f0 <+>: mov eax,DWORD PTR [ebp+0x8]
0x080483f3 <+>: mov edx,DWORD PTR [eax]
0x080483f5 <+>: mov eax,DWORD PTR [ebp+0xc]
0x080483f8 <+>: mov eax,DWORD PTR [eax]
0x080483fa <+>: cmp edx,eax
0x080483fc <+>: je 0x8048405 <cmp+> return FALSE;
0x080483fe <+>: mov eax,0x0
0x08048403 <+>: jmp 0x8048438 <cmp+> if (a->y != b->y)
0x08048405 <+>: mov eax,DWORD PTR [ebp+0x8]
0x08048408 <+>: mov edx,DWORD PTR [eax+0x4]
0x0804840b <+>: mov eax,DWORD PTR [ebp+0xc]
0x0804840e <+>: mov eax,DWORD PTR [eax+0x4]
0x08048411 <+>: cmp edx,eax
0x08048413 <+>: je 0x804841c <cmp+> return FALSE;
0x08048415 <+>: mov eax,0x0
0x0804841a <+>: jmp 0x8048438 <cmp+> if (a->z != b->z)
0x0804841c <+>: mov eax,DWORD PTR [ebp+0x8]
0x0804841f <+>: mov edx,DWORD PTR [eax+0x8]
0x08048422 <+>: mov eax,DWORD PTR [ebp+0xc]
0x08048425 <+>: mov eax,DWORD PTR [eax+0x8]
0x08048428 <+>: cmp edx,eax
0x0804842a <+>: je 0x8048433 <cmp+> return FALSE;
0x0804842c <+>: mov eax,0x0
0x08048431 <+>: jmp 0x8048438 <cmp+> return TRUE;
0x08048433 <+>: mov eax,0x1 }
0x08048438 <+>: pop ebp
0x08048439 <+>: ret End of assembler dump.
(gdb)

o caller: point_t b的地址&b和point_t a的地址&a被依次存入到stack上

              point_t a = { .x = 0x1, .y = 0x2, .z = +0x3 };
0x08048440 <+>: mov DWORD PTR [ebp-0x18],0x1 ; a.x = 0x1
0x08048447 <+>: mov DWORD PTR [ebp-0x14],0x2 ; a.y = 0x2
0x0804844e <+>: mov DWORD PTR [ebp-0x10],0x3 ; a.z = +0x3 point_t b = { .x = 0x1, .y = 0x2, .z = -0x3 };
0x08048455 <+>: mov DWORD PTR [ebp-0xc],0x1 ; b.x = 0x1
0x0804845c <+>: mov DWORD PTR [ebp-0x8],0x2 ; b.y = 0x2
0x08048463 <+>: mov DWORD PTR [ebp-0x4],0xfffffffd ; b.z = -0x3 return !cmp(&a, &b);
0x0804846a <+>: lea eax,[ebp-0xc] ; get &b (addr of struct b)
0x0804846d <+>: mov DWORD PTR [esp+0x4],eax ; save &b to stack
0x08048471 <+>: lea eax,[ebp-0x18] ; get &a (addr of struct a)
0x08048474 <+>: mov DWORD PTR [esp],eax ; save &a to stack
0x08048477 <+>: call 0x80483ed <cmp> ;

显然,在caller中使用cmp(&a, &b)只需要给栈里存入两个值, 相比之下, cmp(a, b)给栈里存入了6个值,cmp(&a, &b) 效率确实高。

另外,在64位的程序中,前6个参数是默认存在寄存器上的,如果超过6个参数,才使用栈传递(具体请参见对应的ABI)。如果使用结构体变量传递参数,对寄存器是极大的浪费。

结论:

  • 不要在函数参数中使用结构体变量;
  • 也不要在函数中定义太多的参数,<=6最好;
  • 如果不可避免地要使用较多的参数,设计函数的时候请最大化利用结构体,然后使用结构体指针作为参数。

最新文章

  1. HDU 1817Necklace of Beads(置换+Polya计数)
  2. 【WP开发】手电筒
  3. LED应用照明产品常识关键点
  4. TouchSlop与VelocityTracker认识
  5. 没有找到 mspdb100.dll 的解决办法
  6. linux centos6.4 php连接sql server2008
  7. js 选择器
  8. iOS设置UITableView中Cell被默认选中后怎么触发didselect事件
  9. (转)Tomcat内存设置详解
  10. 执行 apt-get -f install 提示错误
  11. hihoCoder #1015 : KMP算法【KMP裸题,板子】
  12. 使用Aspose将DataTable转Excel
  13. CQOI2018 简要题解
  14. 使用openbabel进行小分子底物构象搜索
  15. [整理]Assembly中的DLL提取
  16. Java 浅析 Thread.join()
  17. vue.js常用指令
  18. CVE-2018-7600 Drupal核心远程代码执行漏洞分析
  19. 禁用Flash P2P上传
  20. oracle 的分页与 mySQL&#39;的分页转化

热门文章

  1. Delphi 10.1.2 berlin开发跨平台APP的几点经验
  2. ASP.NET Core2利用Jwt技术在服务端实现对客户端的身份认证
  3. 【Newtonsoft.Json.dll】操作简单JSON数据
  4. 菜鸟的Xamarin.Forms前行之路——共享组件
  5. 搜索实时个性化模型——基于FTRL和个性化推荐的搜索排序优化
  6. 《Beginning Java 7》 - 4 - finalize() 手动垃圾回收
  7. 深入了解java虚拟机(JVM) 第二章 内存区域---栈空间
  8. Spring定时任务执行
  9. TCP/IP学习笔记(3)-IP、ARP、RARP协议
  10. 如何在Linux下禁用IPv6