• 在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最好;
    • 如果不可避免地要使用较多的参数,设计函数的时候请最大化利用结构体,然后使用结构体指针作为参数。
  • 相关阅读:
    NetCore使用Log4Net记录日志
    WCF数据协议中XmlArrayItem标签不起作用的问题
    WTM Blazor,Blazor开发利器
    WTM5.0发布,全面支持.net5
    log4netdemo
    mes 入库单号 锁表方案
    线程基础篇-线程同步
    线程基础篇-线程和进程
    EF基础篇-Code First
    EF基础篇-DB First
  • 原文地址:https://www.cnblogs.com/idorax/p/6308068.html
Copyright © 2020-2023  润新知