How do C compilers implement functions that return large structures?

前端 未结 5 2006
长发绾君心
长发绾君心 2020-11-29 05:04

The return value of a function is usually stored on the stack or in a register. But for a large structure, it has to be on the stack. How much copying has to happen in a rea

相关标签:
5条回答
  • 2020-11-29 05:43

    gcc on linux will issue a memcpy() to copy the struct back on the stack of the caller. If the function has internal linkage, more optimizations become available though.

    0 讨论(0)
  • 2020-11-29 05:56

    There are many examples given, but basically

    This question does not have any definite answer. it will depend on the compiler.

    C does not specify how large structs are returned from a function.

    Here's some tests for one particular compiler, gcc 4.1.2 on x86 RHEL 5.4

    gcc trivial case, no copying

    [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 more realistic case , allocate on stack, memcpy to caller

    #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
    

    gcc 4.4.2### has grown a lot, and does not copy for the above non-trivial case.

            .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
    

    In addition, VS2008 (compiled the above as C) will reserve struct Data on the stack of createData() and do a rep movsd loop to copy it back to the caller in Debug mode, in Release mode it will move the return value of rand() (%eax) directly back to the caller

    0 讨论(0)
  • 2020-11-29 05:58
    typedef struct {
        unsigned value[256];
    } Data;
    
    Data createData(void) {
        Data r;
        calcualte(&r);
        return r;
    }
    
    Data d = createData();
    

    msvc(6,8,9) and gcc mingw(3.4.5,4.4.0) will generate code like the following pseudocode

    void createData(Data* r) {
          calculate(&r)
    }
    Data d;
    createData(&d);
    
    0 讨论(0)
  • 2020-11-29 06:02

    None; no copies are done.

    The address of the caller's Data return value is actually passed as a hidden argument to the function, and the createData function simply writes into the caller's stack frame.

    This is known as the named return value optimisation. Also see the c++ faq on this topic.

    commercial-grade C++ compilers implement return-by-value in a way that lets them eliminate the overhead, at least in simple cases

    ...

    When yourCode() calls rbv(), the compiler secretly passes a pointer to the location where rbv() is supposed to construct the "returned" object.

    You can demonstrate that this has been done by adding a destructor with a printf to your struct. The destructor should only be called once if this return-by-value optimisation is in operation, otherwise twice.

    Also you can check the assembly to see that this happens:

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

    here's the assembly:

    __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:
    

    Curiously, it allocated enough space on the stack for the data item subl $1032, %esp, but note that it takes the first argument on the stack 8(%ebp) as the base address of the object, and then initialises element 6 of that item. Since we didn't specify any arguments to createData, this is curious until you realise this is the secret hidden pointer to the parent's version of Data.

    0 讨论(0)
  • 2020-11-29 06:04

    But for a large structure, it has to be on the heap stack.

    Indeed so! A large structure declared as a local variable is allocated on the stack. Glad to have that cleared up.

    As for avoiding copying, as others have noted:

    • Most calling conventions deal with "function returning struct" by passing an additional parameter that points the location in the caller's stack frame in which the struct should be placed. This is definitely a matter for the calling convention and not the language.

    • With this calling convention, it becomes possible for even a relatively simple compiler to notice when a code path is definitely going to return a struct, and for it to fix assignments to that struct's members so that they go directly into the caller's frame and don't have to be copied. The key is for the compiler to notice that all terminating code paths through the function return the same struct variable. If that's the case, the compiler can safely use the space in the caller's frame, eliminating the need for a copy at the point of return.

    0 讨论(0)
提交回复
热议问题