代码之家  ›  专栏  ›  技术社区  ›  Steve Hanov

C编译器如何实现返回大型结构的函数?

  •  22
  • Steve Hanov  · 技术社区  · 15 年前

    函数的返回值通常存储在堆栈或寄存器中。但是对于一个大的结构,它必须在堆栈上。对于这段代码,在一个真正的编译器中,需要进行多少复制?还是优化了?

    例如:

    struct Data {
        unsigned values[256];
    };
    
    Data createData() 
    {
        Data data;
        // initialize data values...
        return data;
    }
    

    (假设函数不能内联..)

    5 回复  |  直到 15 年前
        1
  •  21
  •   Alex Brown    15 年前

    没有;没有副本。

    调用者的数据返回值的地址实际上作为隐藏参数传递给函数,而createdata函数只是写入调用者的堆栈帧。

    这被称为 named return value optimisation . 也看到 c++ faq on this topic .

    商业级C++编译器以值的形式实现返回,从而使它们消除开销,至少在简单的情况下。

    当您的code()调用rbv()时,编译器会秘密地将一个指针传递到rbv()应该在其中构造“返回”对象的位置。

    您可以通过向结构添加带有printf的析构函数来演示这一点。如果此按值返回优化正在运行,则只能调用析构函数一次,否则调用两次。

    此外,您还可以检查程序集,以查看是否发生这种情况:

    Data createData() 
    {
        Data data;
        // initialize data values...
        data.values[5] = 6;
        return data;
    }
    

    下面是程序集:

    __Z10createDatav:
    LFB2:
            pushl   %ebp
    LCFI0:
            movl    %esp, %ebp
    LCFI1:
            subl    $1032, %esp
    LCFI2:
            movl    8(%ebp), %eax
            movl    $6, 20(%eax)
            leave
            ret     $4
    LFE2:
    

    奇怪的是,它在堆栈上为数据项分配了足够的空间 subl $1032, %esp 但请注意,它采用堆栈上的第一个参数 8(%ebp) 作为对象的基地址,然后初始化该项的元素6。由于我们没有为createdata指定任何参数,这很奇怪,直到您意识到这是父级数据版本的秘密隐藏指针。

        2
  •  7
  •   Norman Ramsey    15 年前

    但是对于一个大的结构,它必须在 栈。

    的确如此!在堆栈上分配声明为局部变量的大型结构。很高兴把这件事弄清楚。

    至于避免复制,正如其他人所指出的:

    • 大多数调用约定通过传递一个附加参数来处理“函数返回结构”,该参数指向调用方堆栈帧中应放置结构的位置。这绝对是呼叫约定的问题,而不是语言。

    • 使用这种调用约定,即使是相对简单的编译器也可能注意到,当代码路径肯定要返回一个结构时,它也可以修复对该结构成员的赋值,这样它们就可以直接进入调用方的框架,而不必被复制。关键是让编译器注意到 全部的 通过函数终止代码路径返回 相同的 结构变量。如果是这种情况,编译器可以安全地使用调用方帧中的空间,从而在返回点不需要复制。

        3
  •  6
  •   leeeroy    15 年前

    有很多例子,但基本上

    这个问题没有确切的答案。它将取决于编译器。

    C不指定从函数返回的结构的大小。

    下面是对一个特定编译器的一些测试,即x86 RHEL 5.4上的GCC4.1.2

    GCC普通案例,不复制

    [00:05:21 1 ~] $ gcc -O2 -S -c t.c
    [00:05:23 1 ~] $ cat t.s
            .file   "t.c"
            .text
            .p2align 4,,15
    .globl createData
            .type   createData, @function
    createData:
            pushl   %ebp
            movl    %esp, %ebp
            movl    8(%ebp), %eax
            movl    $1, 24(%eax)
            popl    %ebp
            ret     $4
            .size   createData, .-createData
            .ident  "GCC: (GNU) 4.1.2 20080704 (Red Hat 4.1.2-46)"
            .section        .note.GNU-stack,"",@progbits
    

    GCC更现实的情况,在堆栈上分配,memcpy到调用者

    #include <stdlib.h>
    struct Data {
        unsigned values[256];
    };
    struct Data createData()
    {
        struct Data data;
        int i;
        for(i = 0; i < 256 ; i++)
            data.values[i] = rand();
        return data;
    }
    
    [00:06:08 1 ~] $ gcc -O2 -S -c t.c
    [00:06:10 1 ~] $ cat t.s
            .file   "t.c"
            .text
            .p2align 4,,15
    .globl createData
            .type   createData, @function
    createData:
            pushl   %ebp
            movl    %esp, %ebp
            pushl   %edi
            pushl   %esi
            pushl   %ebx
            movl    $1, %ebx
            subl    $1036, %esp
            movl    8(%ebp), %edi
            leal    -1036(%ebp), %esi
            .p2align 4,,7
    .L2:
            call    rand
            movl    %eax, -4(%esi,%ebx,4)
            addl    $1, %ebx
            cmpl    $257, %ebx
            jne     .L2
            movl    %esi, 4(%esp)
            movl    %edi, (%esp)
            movl    $1024, 8(%esp)
            call    memcpy
            addl    $1036, %esp
            movl    %edi, %eax
            popl    %ebx
            popl    %esi
            popl    %edi
            popl    %ebp
            ret     $4
            .size   createData, .-createData
            .ident  "GCC: (GNU) 4.1.2 20080704 (Red Hat 4.1.2-46)"
            .section        .note.GNU-stack,"",@progbits
    

    一般合同条款第4.4.2款已经增长了很多,并没有复制上述非平凡的情况。

            .file   "t.c"
            .text
            .p2align 4,,15
    .globl createData
            .type   createData, @function
    createData:
            pushl   %ebp
            movl    %esp, %ebp
            pushl   %edi
            pushl   %esi
            pushl   %ebx
            movl    $1, %ebx
            subl    $1036, %esp
            movl    8(%ebp), %edi
            leal    -1036(%ebp), %esi
            .p2align 4,,7
    .L2:
            call    rand
            movl    %eax, -4(%esi,%ebx,4)
            addl    $1, %ebx
            cmpl    $257, %ebx
            jne     .L2
            movl    %esi, 4(%esp)
            movl    %edi, (%esp)
            movl    $1024, 8(%esp)
            call    memcpy
            addl    $1036, %esp
            movl    %edi, %eax
            popl    %ebx
            popl    %esi
            popl    %edi
            popl    %ebp
            ret     $4
            .size   createData, .-createData
            .ident  "GCC: (GNU) 4.1.2 20080704 (Red Hat 4.1.2-46)"
            .section        .note.GNU-stack,"",@progbits
    

    此外,vs2008(将上述编译为c)将在createdata()的堆栈上保留结构数据,并执行 rep movsd 循环以在调试模式下将其复制回调用者,在释放模式下,它将rand()(%eax)的返回值直接移回调用者。

        4
  •  4
  •   OwnWaterloo    15 年前
    typedef struct {
        unsigned value[256];
    } Data;
    
    Data createData(void) {
        Data r;
        calcualte(&r);
        return r;
    }
    
    Data d = createData();
    

    MSVC(6,8,9)和 海湾合作委员会 mingw(3.4.5,4.4.0)将生成类似以下伪代码的代码

    void createData(Data* r) {
          calculate(&r)
    }
    Data d;
    createData(&d);
    
        5
  •  1
  •   nos    15 年前

    Linux上的gcc将发出memcpy()以将结构复制回调用方的堆栈上。如果函数具有内部链接,则可以使用更多的优化。

    推荐文章