Domanda

Il valore di ritorno di una funzione è di solito memorizzato nello stack o in un registro. Ma per una grande struttura, deve essere in pila. Quanto copia deve accadere in un vero e proprio compilatore per questo codice? O è ottimizzato via?

Ad esempio:

struct Data {
    unsigned values[256];
};

Data createData() 
{
    Data data;
    // initialize data values...
    return data;
}

(Assumendo che la funzione non può essere inline ..)

È stato utile?

Soluzione

Nessuno; copie sono fatte.

L'indirizzo del valore di ritorno dei dati del chiamante è in realtà passata come argomento nascosta alla funzione, e la funzione createData scrive semplicemente nella stack frame del chiamante.

Questo è noto come il chiamato valore di ritorno di ottimizzazione . Si veda anche il C ++ faq su questo argomento .

  

di tipo commerciale compilatori C ++ implementano ritorno per valore in un modo che consente loro di eliminare la testa, almeno nei casi più semplici

     

...

     

Quando yourcode () chiama RBV (), il compilatore passa segretamente un puntatore alla posizione in cui si suppone che RBV () per costruire la "restituito" oggetto.

Si può dimostrare che questo è stato fatto con l'aggiunta di un distruttore con una printf alla vostra struct. Il distruttore deve essere chiamato solo una volta, se questa ottimizzazione ritorno per valore è in funzione, altrimenti due volte.

Inoltre è possibile controllare l'assemblaggio di vedere che questo accade:

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

Ecco l'assemblea:

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

Curiosamente, essa è allocata sufficiente spazio sullo stack per la subl $1032, %esp dato, ma si noti che prende il primo argomento sulla 8(%ebp) stack come l'indirizzo di base dell'oggetto, e poi inizializza elemento 6 di tale elemento. Dal momento che non abbiamo specificato alcun argomento a createData, questo è curioso fino a quando si rende conto che è il puntatore segreto nascosto alla versione del genitore di dati.

Altri suggerimenti

  

Ma per una grande struttura, deve essere sulla mucchio stack.

In effetti è così! Una grande struttura dichiarata come variabile locale viene allocata nello stack. Sono contento di avere quel chiarito.

Per quanto per evitare la copia, come altri hanno notato:

  • La maggior parte affare convenzioni di chiamata con "funzione che restituisce struct" passando un parametro aggiuntivo che indica la posizione in stack frame del chiamante in cui deve essere posizionato lo struct. Questo è sicuramente una questione che riguarda la convenzione di chiamata e non la lingua.

  • Con questa convenzione di chiamata, diventa possibile anche per un relativamente semplice compilatore a notare quando un percorso di codice è sicuramente andare a restituire una struct, e per poter fissare le assegnazioni per i membri che di struct in modo che essi vanno direttamente in telaio e del chiamante non devono essere copiati. La chiave è per il compilatore a notare che tutti che chiude percorsi di codice attraverso la funzione di restituire il stesso variabile struct. Se questo è il caso, il compilatore può tranquillamente utilizzare lo spazio nel telaio del chiamante, eliminando la necessità di una copia presso il punto di ritorno.

Ci sono molti esempi, ma in fondo

Questa domanda non ha una risposta definitiva. essa dipenderà dal compilatore.

C non specifica come le strutture di grandi dimensioni sono restituito da una funzione.

Ecco alcuni test per un particolare compilatore, gcc 4.1.2 su x86 RHEL 5.4

gcc caso banale, non la copia

[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 caso più realistico, allocare sullo stack, memcpy al chiamante

#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 ### è cresciuto molto, e non copiare per il caso non banale sopra.

        .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 aggiunta, VS2008 (compilato quanto sopra come C) riserverà struct dati sulla pila di createData () e fare un ciclo rep movsd per copiarlo al chiamante in modalità debug, in modalità di rilascio sposterà il valore di ritorno di rand () (% eax) direttamente al chiamante

typedef struct {
    unsigned value[256];
} Data;

Data createData(void) {
    Data r;
    calcualte(&r);
    return r;
}

Data d = createData();

msvc (6,8,9) e gcc mingw (3.4.5,4.4.0) genererà il codice come il seguente pseudocodice

void createData(Data* r) {
      calculate(&r)
}
Data d;
createData(&d);

gcc su linux emetterà un memcpy () per copiare la parte posteriore struct sulla pila del chiamante. Se la funzione ha collegamento interno, ottimizzazioni rese disponibili però.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top