在C代码中将结构体变量作为参数传递效率忒低

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

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

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

先看下面的代码片段:

o foo1.c

 1 #define FALSE 0
 2 #define TRUE  (!0)
 3
 4 typedef struct point_s {
 5     int x;
 6     int y;
 7     int z;
 8 } point_t;
 9
10 static int cmp(point_t a, point_t b)
11 {
12     if (a.x != b.x)
13         return FALSE;
14     if (a.y != b.y)
15         return FALSE;
16     if (a.z != b.z)
17         return FALSE;
18     return TRUE;
19 }
20
21 int main(int argc, char *argv[])
22 {
23     point_t a = { .x = 0x1, .y = 0x2, .z = +0x3 };
24     point_t b = { .x = 0x1, .y = 0x2, .z = -0x3 };
25     return !cmp(a, b);
26 }

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

 1 $ gcc -g -Wall -m32 -std=gnu99 -o foo1 foo1.c
 2 $ gdb foo1
 3 (gdb) set disassembly-flavor intel
 4 (gdb) disas /m main
 5 Dump of assembler code for function main:
 6 22    {
 7    0x0804842a <+0>:    push   ebp
 8    0x0804842b <+1>:    mov    ebp,esp
 9    0x0804842d <+3>:    sub    esp,0x38
10
11 23        point_t a = { .x = 0x1, .y = 0x2, .z = +0x3 };
12    0x08048430 <+6>:    mov    DWORD PTR [ebp-0x18],0x1
13    0x08048437 <+13>:    mov    DWORD PTR [ebp-0x14],0x2
14    0x0804843e <+20>:    mov    DWORD PTR [ebp-0x10],0x3
15
16 24        point_t b = { .x = 0x1, .y = 0x2, .z = -0x3 };
17    0x08048445 <+27>:    mov    DWORD PTR [ebp-0xc],0x1
18    0x0804844c <+34>:    mov    DWORD PTR [ebp-0x8],0x2
19    0x08048453 <+41>:    mov    DWORD PTR [ebp-0x4],0xfffffffd
20
21 25        return !cmp(a, b);
22    0x0804845a <+48>:    mov    eax,DWORD PTR [ebp-0xc]
23    0x0804845d <+51>:    mov    DWORD PTR [esp+0xc],eax
24    0x08048461 <+55>:    mov    eax,DWORD PTR [ebp-0x8]
25    0x08048464 <+58>:    mov    DWORD PTR [esp+0x10],eax
26    0x08048468 <+62>:    mov    eax,DWORD PTR [ebp-0x4]
27    0x0804846b <+65>:    mov    DWORD PTR [esp+0x14],eax
28    0x0804846f <+69>:    mov    eax,DWORD PTR [ebp-0x18]
29    0x08048472 <+72>:    mov    DWORD PTR [esp],eax
30    0x08048475 <+75>:    mov    eax,DWORD PTR [ebp-0x14]
31    0x08048478 <+78>:    mov    DWORD PTR [esp+0x4],eax
32    0x0804847c <+82>:    mov    eax,DWORD PTR [ebp-0x10]
33    0x0804847f <+85>:    mov    DWORD PTR [esp+0x8],eax
34    0x08048483 <+89>:    call   0x80483ed <cmp>
35    0x08048488 <+94>:    test   eax,eax
36    0x0804848a <+96>:    sete   al
37    0x0804848d <+99>:    movzx  eax,al
38
39 26    }
40    0x08048490 <+102>:    leave
41    0x08048491 <+103>:    ret
42
43 End of assembler dump.
44 (gdb) disas /m cmp
45 Dump of assembler code for function cmp:
46 11    {
47    0x080483ed <+0>:    push   ebp
48    0x080483ee <+1>:    mov    ebp,esp
49
50 12        if (a.x != b.x)
51    0x080483f0 <+3>:    mov    edx,DWORD PTR [ebp+0x8]
52    0x080483f3 <+6>:    mov    eax,DWORD PTR [ebp+0x14]
53    0x080483f6 <+9>:    cmp    edx,eax
54    0x080483f8 <+11>:    je     0x8048401 <cmp+20>
55
56 13            return FALSE;
57    0x080483fa <+13>:    mov    eax,0x0
58    0x080483ff <+18>:    jmp    0x8048428 <cmp+59>
59
60 14        if (a.y != b.y)
61    0x08048401 <+20>:    mov    edx,DWORD PTR [ebp+0xc]
62    0x08048404 <+23>:    mov    eax,DWORD PTR [ebp+0x18]
63    0x08048407 <+26>:    cmp    edx,eax
64    0x08048409 <+28>:    je     0x8048412 <cmp+37>
65
66 15            return FALSE;
67    0x0804840b <+30>:    mov    eax,0x0
68    0x08048410 <+35>:    jmp    0x8048428 <cmp+59>
69
70 16        if (a.z != b.z)
71    0x08048412 <+37>:    mov    edx,DWORD PTR [ebp+0x10]
72    0x08048415 <+40>:    mov    eax,DWORD PTR [ebp+0x1c]
73    0x08048418 <+43>:    cmp    edx,eax
74    0x0804841a <+45>:    je     0x8048423 <cmp+54>
75
76 17            return FALSE;
77    0x0804841c <+47>:    mov    eax,0x0
78    0x08048421 <+52>:    jmp    0x8048428 <cmp+59>
79
80 18        return TRUE;
81    0x08048423 <+54>:    mov    eax,0x1
82
83 19    }
84    0x08048428 <+59>:    pop    ebp
85    0x08048429 <+60>:    ret
86
87 End of assembler dump.
88 (gdb)

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

23              point_t a = { .x = 0x1, .y = 0x2, .z = +0x3 };
   0x08048430 <+6>:     mov    DWORD PTR [ebp-0x18],0x1         ; a.x = 0x1
   0x08048437 <+13>:    mov    DWORD PTR [ebp-0x14],0x2         ; a.y = 0x2
   0x0804843e <+20>:    mov    DWORD PTR [ebp-0x10],0x3         ; a.z = +0x3

24              point_t b = { .x = 0x1, .y = 0x2, .z = -0x3 };
   0x08048445 <+27>:    mov    DWORD PTR [ebp-0xc],0x1          ; b.x = 0x1
   0x0804844c <+34>:    mov    DWORD PTR [ebp-0x8],0x2          ; b.y = 0x2
   0x08048453 <+41>:    mov    DWORD PTR [ebp-0x4],0xfffffffd   ; b.z = -0x3

25              return !cmp(a, b);
   0x0804845a <+48>:    mov    eax,DWORD PTR [ebp-0xc]          ;
   0x0804845d <+51>:    mov    DWORD PTR [esp+0xc],eax          ; save b.x to stack
   0x08048461 <+55>:    mov    eax,DWORD PTR [ebp-0x8]          ;
   0x08048464 <+58>:    mov    DWORD PTR [esp+0x10],eax         ; save b.y to stack
   0x08048468 <+62>:    mov    eax,DWORD PTR [ebp-0x4]          ;
   0x0804846b <+65>:    mov    DWORD PTR [esp+0x14],eax         ; save b.z to stack
   0x0804846f <+69>:    mov    eax,DWORD PTR [ebp-0x18]         ;
   0x08048472 <+72>:    mov    DWORD PTR [esp],eax              ; save a.x to stack
   0x08048475 <+75>:    mov    eax,DWORD PTR [ebp-0x14]         ;
   0x08048478 <+78>:    mov    DWORD PTR [esp+0x4],eax          ; save a.y to stack
   0x0804847c <+82>:    mov    eax,DWORD PTR [ebp-0x10]         ;
   0x0804847f <+85>:    mov    DWORD PTR [esp+0x8],eax          ; save a.z to stack
   0x08048483 <+89>:    call   0x80483ed <cmp>                  ;

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

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

o foo2.c

 1 #define FALSE 0
 2 #define TRUE  (!0)
 3
 4 typedef struct point_s {
 5     int x;
 6     int y;
 7     int z;
 8 } point_t;
 9
10 static int cmp(point_t *a, point_t *b)
11 {
12     if (a->x != b->x)
13         return FALSE;
14     if (a->y != b->y)
15         return FALSE;
16     if (a->z != b->z)
17         return FALSE;
18     return TRUE;
19 }
20
21 int main(int argc, char *argv[])
22 {
23     point_t a = { .x = 0x1, .y = 0x2, .z = +0x3 };
24     point_t b = { .x = 0x1, .y = 0x2, .z = -0x3 };
25     return !cmp(&a, &b);
26 }

o foo1.c v.s. foo2.c

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

 1 $ gcc -g -Wall -m32 -std=gnu99 -o foo2 foo2.c
 2 $ gdb foo2
 3 (gdb) set disassembly-flavor intel
 4 (gdb) disas /m main
 5 Dump of assembler code for function main:
 6 22      {
 7    0x0804843a <+0>:     push   ebp
 8    0x0804843b <+1>:     mov    ebp,esp
 9    0x0804843d <+3>:     sub    esp,0x28
10
11 23              point_t a = { .x = 0x1, .y = 0x2, .z = +0x3 };
12    0x08048440 <+6>:     mov    DWORD PTR [ebp-0x18],0x1
13    0x08048447 <+13>:    mov    DWORD PTR [ebp-0x14],0x2
14    0x0804844e <+20>:    mov    DWORD PTR [ebp-0x10],0x3
15
16 24              point_t b = { .x = 0x1, .y = 0x2, .z = -0x3 };
17    0x08048455 <+27>:    mov    DWORD PTR [ebp-0xc],0x1
18    0x0804845c <+34>:    mov    DWORD PTR [ebp-0x8],0x2
19    0x08048463 <+41>:    mov    DWORD PTR [ebp-0x4],0xfffffffd
20
21 25              return !cmp(&a, &b);
22    0x0804846a <+48>:    lea    eax,[ebp-0xc]
23    0x0804846d <+51>:    mov    DWORD PTR [esp+0x4],eax
24    0x08048471 <+55>:    lea    eax,[ebp-0x18]
25    0x08048474 <+58>:    mov    DWORD PTR [esp],eax
26    0x08048477 <+61>:    call   0x80483ed <cmp>
27    0x0804847c <+66>:    test   eax,eax
28    0x0804847e <+68>:    sete   al
29    0x08048481 <+71>:    movzx  eax,al
30
31 26      }
32    0x08048484 <+74>:    leave
33    0x08048485 <+75>:    ret
34
35 End of assembler dump.
36 (gdb) disas /m cmp
37 Dump of assembler code for function cmp:
38 11      {
39    0x080483ed <+0>:     push   ebp
40    0x080483ee <+1>:     mov    ebp,esp
41
42 12              if (a->x != b->x)
43    0x080483f0 <+3>:     mov    eax,DWORD PTR [ebp+0x8]
44    0x080483f3 <+6>:     mov    edx,DWORD PTR [eax]
45    0x080483f5 <+8>:     mov    eax,DWORD PTR [ebp+0xc]
46    0x080483f8 <+11>:    mov    eax,DWORD PTR [eax]
47    0x080483fa <+13>:    cmp    edx,eax
48    0x080483fc <+15>:    je     0x8048405 <cmp+24>
49
50 13                      return FALSE;
51    0x080483fe <+17>:    mov    eax,0x0
52    0x08048403 <+22>:    jmp    0x8048438 <cmp+75>
53
54 14              if (a->y != b->y)
55    0x08048405 <+24>:    mov    eax,DWORD PTR [ebp+0x8]
56    0x08048408 <+27>:    mov    edx,DWORD PTR [eax+0x4]
57    0x0804840b <+30>:    mov    eax,DWORD PTR [ebp+0xc]
58    0x0804840e <+33>:    mov    eax,DWORD PTR [eax+0x4]
59    0x08048411 <+36>:    cmp    edx,eax
60    0x08048413 <+38>:    je     0x804841c <cmp+47>
61
62 15                      return FALSE;
63    0x08048415 <+40>:    mov    eax,0x0
64    0x0804841a <+45>:    jmp    0x8048438 <cmp+75>
65
66 16              if (a->z != b->z)
67    0x0804841c <+47>:    mov    eax,DWORD PTR [ebp+0x8]
68    0x0804841f <+50>:    mov    edx,DWORD PTR [eax+0x8]
69    0x08048422 <+53>:    mov    eax,DWORD PTR [ebp+0xc]
70    0x08048425 <+56>:    mov    eax,DWORD PTR [eax+0x8]
71    0x08048428 <+59>:    cmp    edx,eax
72    0x0804842a <+61>:    je     0x8048433 <cmp+70>
73
74 17                      return FALSE;
75    0x0804842c <+63>:    mov    eax,0x0
76    0x08048431 <+68>:    jmp    0x8048438 <cmp+75>
77
78 18              return TRUE;
79    0x08048433 <+70>:    mov    eax,0x1
80
81 19      }
82    0x08048438 <+75>:    pop    ebp
83    0x08048439 <+76>:    ret
84
85 End of assembler dump.
86 (gdb)

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

23              point_t a = { .x = 0x1, .y = 0x2, .z = +0x3 };
   0x08048440 <+6>:     mov    DWORD PTR [ebp-0x18],0x1         ; a.x = 0x1
   0x08048447 <+13>:    mov    DWORD PTR [ebp-0x14],0x2         ; a.y = 0x2
   0x0804844e <+20>:    mov    DWORD PTR [ebp-0x10],0x3         ; a.z = +0x3

24              point_t b = { .x = 0x1, .y = 0x2, .z = -0x3 };
   0x08048455 <+27>:    mov    DWORD PTR [ebp-0xc],0x1          ; b.x = 0x1
   0x0804845c <+34>:    mov    DWORD PTR [ebp-0x8],0x2          ; b.y = 0x2
   0x08048463 <+41>:    mov    DWORD PTR [ebp-0x4],0xfffffffd   ; b.z = -0x3

25              return !cmp(&a, &b);
   0x0804846a <+48>:    lea    eax,[ebp-0xc]                    ; get &b (addr of struct b)
   0x0804846d <+51>:    mov    DWORD PTR [esp+0x4],eax          ; save &b to stack
   0x08048471 <+55>:    lea    eax,[ebp-0x18]                   ; get &a (addr of struct a)
   0x08048474 <+58>:    mov    DWORD PTR [esp],eax              ; save &a to stack
   0x08048477 <+61>:    call   0x80483ed <cmp>                  ;

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

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

结论:

  • 不要在函数参数中使用结构体变量;
  • 也不要在函数中定义太多的参数,定义6个及其以下最好;
  • 如果不可避免使用较多的参数,设计函数的时候请最大化使用结构体,然后传递结构体变量的地址。
时间: 2024-11-03 21:58:31

在C代码中将结构体变量作为参数传递效率忒低的相关文章

使用结构体变量存储学生姓名和课程成绩,计算平均分及查找不及格同学姓名及分数

1 struct Student //声明Student类型结构体变量 2 { 3 char name[20]; 4 int score[5]; 5 }; 6 typedef struct Student Stu; 7 8 void main() 9 { 10 11 Stu stu[3]; 定义Student类型变量数组 12 int i,j; 13 for(i=0;i<3;i++) //输入学生姓名和成绩 14 { 15 printf("please input student[%d]'

c++指向结构体变量指针

转自http://www.jb51.net/article/42140.htm 定义: 结构体变量的指针就是该变来那个所占据的内存段的起始地址.可以设一个指针变量,来指向一个结构体变量,此时该指针变量的值是结构体变量的起始地址. 设p是指向结构体变量的数组,则可以通过以下的方式,调用指向的那个结构体中的成员: (1)结构体变量.成员名.如,stu.num. (2)(*p).成员名.如,(*p).num. (3)p->成员名.如,p->num. 代码如下: #include<iostrea

(struct)结构体变量作为函数参数调用的方法小结

结构体变量.结构指针变量.结构数组作为函数的参数应用实例分析 struct stud { long int num; float score; }; /*结构体变量作为函数的参数,修改之后的成员值不能返回到主调函数*/ void funvr(struct stud t) { t.num=2000101; t.score=71.0; } /*结构体数组作为函数的参数,修改后的元素的成员值能返回到主调函数*/ void funar(struct stud t[]) //void funar(stru

C语言中两个相同类型的结构体变量之间是可以相互直接赋值的

C语言中,在相同类型的变量间赋值时是直接内存复制的,即将他们的内存进行复制,而两个同类型的结构体变量属于同一种变量,所以赋值时是按照他们的内存分布来直接拷贝的.所以,在C语言中两个相同类型的结构体变量之间是可以相互赋值的.但是要注意指针的浅层复制问题. 下面是一个简单的验证代码: #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdlib.h> struct test

用结构体指针指向(-&gt;)或结构体变量加点(.)后不出现结构体成员

今天写代码时遇到这么个问题:用结构体指针指向(->)或结构体变量加点(.)后不出现结构体成员,虽然不影响编写,但效率降低,容易出错. 代码入下: stack.h #ifndef __STACK_H__ #define __STACK_H__ #include<stdlib.h> #include<iostream> using namespace std; #define STACK_DEFAULT_SIZE 10; typedef int ElemType; typedef

EDKII CR宏:根据成员指针获取父结构体变量指针

核心提示: 1. CR宏 (Containing Record):根据成员指针获取父结构体变量指针 2. 0 指针的妙用. 在EDKII 的代码中有不少关于CR宏的使用,如 时钟中断处理函数CoreTimerTick. VOID EFIAPI CoreTimerTick ( IN UINT64 Duration ) { IEVENT *Event; ... if (!IsListEmpty (&mEfiTimerList)) { Event = CR (mEfiTimerList.Forward

C/C++第一天开班,序章:结构体变量声明、实例化及输出

今天第一天开班,以后会不定期更新C/C++的基础内容,希望大家能多多喜欢,若大家有好的建议,请给我留言. 今天的主题:结构体变量声明.实例化及输出 代码如下: 1 #include<stdio.h> 2 #include<string.h> 3 #include<stdlib.h> 4 5 struct People{ 6 char Name[10]; 7 int Age; 8 };//定义包含名称和年龄字段的结构体People 9 10 int main(){ 11

用结构体变量和指向结构体变量的指针构成链表

测试描述:建立一个简单的静态链表,它由3个学生数据的结点组成.输出各结点中的数据. 代码如下: #include <iostream> using namespace std; struct Student { int num; float score; Student *next; }; int main() { Student a,b,c,*head,*p;//都是Student类型的结构体变量 a.num=1001;a.score=89.5; b.num=1002;b.score=90.

C语言中结构体变量之间赋值

最近,我阅读了某新员工小刘写的C语言代码,发现其对结构体变量之间的赋值不是很熟悉.对于两个相同类型的结构体变量,他均采用的是逐个成员变量直接赋值的形式.如下的代码示例: 如上代码所示,tEmployeeInfoSrc和tEmployeeInfoDest是两个类型相同的结构体变量,tEmployeeInfoSrc(源结构体变量)中的各个成员变量已赋初值.现要使tEmployeeInfoDest(目的结构体变量)中的各个成员变量的值与tEmployeeInfoSrc中对应成员变量的值相同.小刘采用的