C 语言关于结构体做参数传递?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C 语言关于结构体做参数传递?相关的知识,希望对你有一定的参考价值。

学链表的时候,函数传递参数用了值传递,按理说按值传递,不是不会改变主函数里的输出结果吗,可遍历节点的时候,发现结果还是变了。学生一枚,求大神解释一下

参考技术A 双指针
C语言结构体传参

小-黯
原创
关注
7点赞·2315人阅读
目录
C语言结构体传参
1. 普通传参
1.1 测试代码
1.2 测试结果
1.3 结果分析
2. 单指针传参
2.1 修改结构体数据
2.1.1 测试代码
2.1.2 测试结果
2.1.3 结果分析
2.2 修改结构体地址
2.2.1 测试代码
2.2.2 测试结果
2.2.3 结果分析
3. 双指针传参
3.1 测试代码
3.2 测试结果
3.2 结果分析
C语言结构体传参
结构体传参包括三种传参方式

普通传参:函数接收到结构体参数后,会复制一份原来的结构体作为形参供函数使用,而函数内的任何操作都是对拷贝结构体的修改,不会影响到原本的结构体变化。
单指针传参:函数接收的是一个结构体的地址,该指针指向的是结构体起始地址,也就相当于传入了结构体内所有变量的地址,函数接收到该结构体指针后,我们就可以根据地址访问结构体中每个变量的真实数据,在函数内对结构体内数据的操作,都会影响到原本结构体内数据的变化
双指针传参:函数接收的是结构体指针变量的地址,因为一级指针代表的是结构体的地址,在函数中能够操作结构体内的数据,则二级指针指向的是结构体的地址,则同理我们可以根据二级指针访问修改结构体的地址
即通过一级指针,对结构体内数据的操作会影响到原本结构体内数据的变化
而通过二级指针,对结构体地址的操作会影响到原本结构体地址的变化,例如为结构体分配空间

在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个参数,才使用栈传递(具体请参见对应的ABI)。如果使用结构体变量传递参数,对寄存器是极大的浪费。

结论:

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

以上是关于C 语言关于结构体做参数传递?的主要内容,如果未能解决你的问题,请参考以下文章

C语言中,结构体作为函数参数传递,详解?

在C语言中参数是如何传递的?

关于Wscript.Shell中 Run参数传递

关于Wscript.Shell中 Run参数传递

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

Python向怎么向C语言传递结构体