在C代码中将结构体变量作为参数传递效率忒低
2024-08-27 11:52:56
在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最好;
- 如果不可避免地要使用较多的参数,设计函数的时候请最大化利用结构体,然后使用结构体指针作为参数。
最新文章
- HDU 1817Necklace of Beads(置换+Polya计数)
- 【WP开发】手电筒
- LED应用照明产品常识关键点
- TouchSlop与VelocityTracker认识
- 没有找到 mspdb100.dll 的解决办法
- linux centos6.4 php连接sql server2008
- js 选择器
- iOS设置UITableView中Cell被默认选中后怎么触发didselect事件
- (转)Tomcat内存设置详解
- 执行 apt-get -f install 提示错误
- hihoCoder #1015 : KMP算法【KMP裸题,板子】
- 使用Aspose将DataTable转Excel
- CQOI2018 简要题解
- 使用openbabel进行小分子底物构象搜索
- [整理]Assembly中的DLL提取
- Java 浅析 Thread.join()
- vue.js常用指令
- CVE-2018-7600 Drupal核心远程代码执行漏洞分析
- 禁用Flash P2P上传
- oracle 的分页与 mySQL&#39;的分页转化
热门文章
- Delphi 10.1.2 berlin开发跨平台APP的几点经验
- ASP.NET Core2利用Jwt技术在服务端实现对客户端的身份认证
- 【Newtonsoft.Json.dll】操作简单JSON数据
- 菜鸟的Xamarin.Forms前行之路——共享组件
- 搜索实时个性化模型——基于FTRL和个性化推荐的搜索排序优化
- 《Beginning Java 7》 - 4 - finalize() 手动垃圾回收
- 深入了解java虚拟机(JVM) 第二章 内存区域---栈空间
- Spring定时任务执行
- TCP/IP学习笔记(3)-IP、ARP、RARP协议
- 如何在Linux下禁用IPv6