2015-05-08 10 views
14

Quando si passano gli argomenti a una funzione, ho sempre pensato che passare argomenti uno per uno non sia diverso dal passarli racchiusi in un array o una struct o una tuple. Tuttavia, un semplice esperimento ha dimostrato che avevo torto.Passare i parametri uno ad uno o avvolgerli in un array, struct o tuple

Il seguente programma quando compiled with GCC:

int test(int a, int b, int c, int d) { 
    return a + b + c + d; 
} 

int test(std::array<int, 4> arr) { 
    return arr[0] + arr[1] + arr[2] + arr[3]; 
} 

struct abcd { 
    int a; int b; int c; int d; 
}; 

int test(abcd s) { 
    return s.a + s.b + s.c + s.d; 
} 

int test(std::tuple<int, int, int, int> tup) { 
    return std::get<0>(tup) + std::get<1>(tup) + std::get<2>(tup) + std::get<3>(tup); 
} 

... produce una varietà di uscite di montaggio:

impl_test(int, int, int, int): 
    lea eax, [rdi+rsi] 
    add eax, edx 
    add eax, ecx 
    ret 

impl_test(std::array<int, 4ul>): 
    mov rax, rdi 
    sar rax, 32 
    add eax, edi 
    add eax, esi 
    sar rsi, 32 
    add eax, esi 
    ret 

impl_test(abcd): 
    mov rax, rdi 
    sar rax, 32 
    add eax, edi 
    add eax, esi 
    sar rsi, 32 
    add eax, esi 
    ret 

impl_test(std::tuple<int, int, int, int>): 
    mov eax, DWORD PTR [rdi+8] 
    add eax, DWORD PTR [rdi+12] 
    add eax, DWORD PTR [rdi+4] 
    add eax, DWORD PTR [rdi] 
    ret 

main: 
    push rbp 
    push rbx 
    mov ecx, 4 
    mov edx, 3 
    movabs rbp, 8589934592 
    mov esi, 2 
    sub rsp, 24 
    mov edi, 1 
    movabs rbx, 17179869184 
    call int test<int, int, int, int>(int, int, int, int) 

    mov rdi, rbp 
    mov rsi, rbx 
    or rbx, 3 
    or rdi, 1 
    or rsi, 3 
    call int test<std::array<int, 4ul> >(std::array<int, 4ul>) 

    mov rdi, rbp 
    mov rsi, rbx 
    or rdi, 1 
    call int test<abcd>(abcd) 

    mov rdi, rsp 
    mov DWORD PTR [rsp], 4 
    mov DWORD PTR [rsp+4], 3 
    mov DWORD PTR [rsp+8], 2 
    mov DWORD PTR [rsp+12], 1 
    call int test<std::tuple<int, int, int, int> >(std::tuple<int, int, int, int>) 

    add rsp, 24 
    xor eax, eax 
    pop rbx 
    pop rbp 
    ret 

Perché c'è una differenza?

+0

I compilatori non possono generalmente fare quello che vogliono purché il risultato finale sia lo stesso? – AndyG

+1

@AndyG Sì e no. Ci sono anche convenzioni di chiamata basate sia sul linguaggio che sulla piattaforma che determinano come vengono chiamate le cose. Senza tali elementi (come gli argomenti passati in registri da destra a sinistra) sarebbe impossibile collegare vari file di oggetti insieme da lingue di origine diverse e produrre codice affidabile. –

+0

La struttura * viene * inoltrata come se i singoli membri fossero separati. Ma l'ottimizzatore (o la mancanza di) sta facendo qualcosa di strano con la conversione a 64 bit verso il basso a 32 bit. – Mysticial

risposta

-1

Sono abbastanza sicuro che la regola in C++ come nella vecchia C è: Per un array o un riferimento di struttura, è il puntatore all'originale che viene inviato come parametro e non un duplicato. e per quanto posso leggere il codice assembly, è proprio quello che sta succedendo. 1) tutto sullo stack in valori duplicati. 2) Il puntatore all'array nello stack 3) Il puntatore alla struttura nello stack 4) Sembra che le tuple siano una forma di struttura che sono effettivamente posti nello stack. (e sono in effetti alcuni in mezzo).

+0

"per quanto posso leggere il codice assembly" - non deve essere molto lontano. Le prime tre versioni non hanno mai caricato * nulla * dalla memoria (o "lo stack"). –

1

Bene, hai semplificato un po 'come gli argomenti vengono passati.

Quando una funzione viene chiamata (ovvero non inline, constexpr valutata o eliminata), il modo in cui gli argomenti vengono passati dipende dai seguenti fattori: 1- Se l'argomento è un numero intero o un valore a virgola mobile se l'argomento è di un tipo primitivo. 2- La dimensione dell'argomento. 3- Se il suo indirizzo è preso in qualche codice non eliminato nel callee. 4- La convenzione di chiamata. 5- Se viene utilizzata l'intera ottimizzazione del programma (WPO). 6- Se il callee è collegato esternamente o staticamente. 7- Se il callee viene esportato o meno. 8- Il comportamento in virgola mobile specificato. 9- La piattaforma di destinazione. 10- Se l'argomento è noto in fase di compilazione. 11- Se l'argomento è usato in un modo "semplice" secondo il compilatore. 12- Il numero di parametri. 13- La posizione del parametro nella lista parametri. 14- Il tipo dell'argomento. Il compilatore può gestire i tipi familiari in modo più efficiente.

Ora torniamo all'esempio che hai fornito. Hai compilato il codice con -02 in modo che il codice morto non venga eliminato e la funzione di inlining sia disabilitata. Quindi tutte le funzioni devono essere chiamate. La piattaforma di destinazione è x64.

La prima funzione ha quattro parametri integer a 4 byte. Pertanto, tutti loro sono passati attraverso i registri.

La seconda funzione ha una matrice di dimensioni fisse di quattro numeri interi a 4 byte. Il compilatore ha deciso di utilizzare due registri (rdi e rsi) per passare i quattro numeri interi in cui rdi = 0x200000001 e rsi = 0x400000003. Notate come i quattro numeri interi (1, 2, 3, 4) vengono passati in modo compatto usando questi due registri.

Passando gli interi come struttura piuttosto che uno ad uno, il compilatore ha utilizzato tecniche diverse per passarli. Ma qui c'è un compromesso tra la dimensione del codice, la velocità e il numero di registri richiesti.

La stessa cosa vale per la terza funzione.

L'ultima funzione, tuttavia, contiene chiamate a std :: get che richiedono l'indirizzo della tupla passata. Quindi l'indirizzo è memorizzato in rdi per essere usato dalla funzione std :: get. Dal momento che stai compilando con C++ 14, std :: get è contrassegnato con constexpr. Il compilatore è stato in grado di valutare la funzione e quindi l'accesso alla memoria è stato emesso nella funzione di test piuttosto che emettere una chiamata alla funzione std :: get. Si noti che questo è diverso dall'inlining.

Problemi correlati