2010-05-29 15 views
15

Questa domanda è nata da una programmazione in misti. Ho avuto una routine Fortran che volevo chiamare dal codice C++. Fortran passa tutti i suoi parametri per riferimento (a meno che tu non lo dica diversamente).In che modo i compilatori C++ passano effettivamente i parametri di riferimento?

così ho pensato che sarei intelligente (brutta partenza proprio lì) nel mio codice C++ e definire il Fortran qualcosa di routine, come questo:

extern "C" void FORTRAN_ROUTINE (unsigned & flag); 

Questo codice ha funzionato per un po ', ma (ovviamente ragione quando Avevo bisogno di partire) improvvisamente ha iniziato a saltare su una chiamata di ritorno. Chiara indicazione di una pila di chiamate mungite.

un altro ingegnere è venuto dietro di me e ha risolto il problema, dichiarando che la routine avevano da definire in C++ come

extern "C" void FORTRAN_ROUTINE (unsigned * flag); 

mi piacerebbe accettare che ad eccezione di due cose. Uno è che sembra piuttosto contro-intuitivo per il compilatore di non passare i parametri di riferimento come riferimento, e non trovo documentazione da nessuna parte che lo dice. L'altro è che ha cambiato un'intera serie di altri codici nello stesso momento, quindi teoricamente avrebbe potuto essere un altro cambiamento che risolveva qualsiasi problema.

Quindi la domanda è: in che modo C++ passa effettivamente i parametri di riferimento? È forse libero di fare copia, copia per piccoli valori o qualcosa del genere? In altre parole, i parametri di riferimento sono assolutamente inutili nella programmazione in linguaggio misto? Mi piacerebbe saperlo, così non commetterò mai più lo stesso errore di codice.

+0

i riferimenti e i puntatori sono la stessa cosa quando si supera la fase di convalida: http://stackoverflow.com/questions/1640355/whats-the-low-level-difference-between-a-pointer-an-a- riferimento –

+0

@CrazyJugglerDrummer: Da quello che ho letto lì, la risposta è in realtà "non necessariamente, ma con la maggior parte dei compilatori oggi sì". Direi che è la stessa domanda della mia, tranne che ho aggiunto la ruga di avere la routine in questione definita come "extern" C "'. Questo forse li rende magicamente uguali? –

risposta

4

Solo per entrare, credo che tu abbia ragione. Io uso i riferimenti per passare i parametri alle funzioni Fortran tutto il tempo. Nella mia esperienza, l'uso di riferimenti o puntatori all'interfaccia Fortran-C++ è equivalente. Ho provato questo utilizzando GCC/Gfortran e Visual Studio/Intel Visual Fortran. Potrebbe essere dipendente dal compilatore, ma penso che in pratica tutti i compilatori implementino i riferimenti passando il puntatore.

+0

Stiamo usando anche VS/IVF, quindi questo è un commento molto utile per me. –

9

C++ non definisce come dovrebbero essere le implementazioni, è solo una lingua. Quindi non esiste una "a" implementazione di riferimenti.

Detto questo, i riferimenti vengono implementati con i puntatori. Ciò porta ad un sacco di confusione ("i riferimenti sono solo dei puntatori", "i riferimenti sono solo degli indicatori con le parti banali estratte") ma quello è non il caso. I riferimenti sono alias e saranno sempre alias.

Il compilatore passerà l'indirizzo di una variabile e opererà con quel puntatore. Questo ha lo stesso effetto (ma non la stessa semantica!). Per essere più concreto, un compilatore potrebbe "sostituire" questo:

void make_five(int& i) 
{ 
    i = 5; 
} 

int main(void) 
{ 
    int i = 0; 
    make_five(i); 
} 

Con questo:

void make_five(int* const i) 
{ 
    *i = 5; 
} 

int main(void) 
{ 
    int i = 0; 
    make_five(&i); 
} 

(In pratica sarebbe inline una funzione così semplice, ma si ottiene il punto.) Quindi perché il tuo collega ti ha suggerito di usare un puntatore.

Ricordare che i riferimenti devono essere preferiti. È qui che la distinzione tra riferimenti e puntatori è importante. Vuoi fare l'alias di una variabile o vuoi puntarla? La maggior parte delle volte, la prima. In C, dovevi usare un puntatore per fare questo, e questo contribuisce al malinteso comune del programmatore C che i riferimenti sono in realtà dei puntatori.

Per ottenere la semantica simili (dal momento che si sta ora puntando ad una variabile, e non aliasing esso), si dovrebbe garantire il valore del puntatore non è nullo:

extern "C" void FORTRAN_ROUTINE (unsigned * flag) 
{ 
    assert(flag); // this is normally not a problem with references, 
        // since the address of a variable cannot be null. 

    // continue... 
} 

giusto per essere sicuri.

+0

OK, ma è quello che mi aspettavo che facesse. Se questo si blocca, allora deve fare qualcos'altro. È possibile? Cosa sarebbe qualcos'altro? –

+0

Si noti che la dichiarazione 'extern' è solo uno stub. L'implementazione è in Fortran. Il lato Fortran * non * passa un puntatore. Sta passando un 'logico * 4' (quattro byte booleano) * per riferimento *. Non sarà mai un puntatore nullo. Questo è garantito. Ecco perché sembrava che un parametro di riferimento fosse più adatto di un puntatore. –

+0

@ T.E.D .: Non ho avuto a che fare con l'interfacciamento di Fortran, ma molto probabilmente ha le esportazioni in stile C che usano i puntatori per il pass-by-reference e si è appena verificato che funzioni per te in determinate circostanze. –

2

In teoria, in C++ i riferimenti sono implementati come normali puntatori. Il compilatore cambia quindi il codice per la funzione tonehave come un riferimento, ma carica l'indirizzo e quindi sposta indirettamente l'indirizzo.

Ecco una piccola applicazione:

void foo(int & value) 
{ 
    value = 3; 
} 


void bar(int *value) 
{ 
    *value = 3; 
} 

void do_test() 
{ 
    int i; 
    foo(i); 
    bar(&i); 
} 

Lets assemblare e cerca nella gcc generato montare (GCC -s):

 .file "test-params.cpp" 
     .text 
.globl _Z3fooRi 
     .type _Z3fooRi, @function 
_Z3fooRi: 
.LFB0: 
     .cfi_startproc 
     .cfi_personality 0x0,__gxx_personality_v0 
     pushl %ebp 
     .cfi_def_cfa_offset 8 
     movl %esp, %ebp 
     .cfi_offset 5, -8 
     .cfi_def_cfa_register 5 
     movl 8(%ebp), %eax 
     movl $3, (%eax) 
     popl %ebp 
     ret 
     .cfi_endproc 
.LFE0: 
     .size _Z3fooRi, .-_Z3fooRi 
.globl _Z3barPi 
     .type _Z3barPi, @function 
_Z3barPi: 
.LFB1: 
     .cfi_startproc 
     .cfi_personality 0x0,__gxx_personality_v0 
     pushl %ebp 
     .cfi_def_cfa_offset 8 
     movl %esp, %ebp 
     .cfi_offset 5, -8 
     .cfi_def_cfa_register 5 
     movl 8(%ebp), %eax 
     movl $3, (%eax) 
     popl %ebp 
     ret 
     .cfi_endproc 
.LFE1: 
     .size _Z3barPi, .-_Z3barPi 
.globl _Z7do_testv 
     .type _Z7do_testv, @function 
_Z7do_testv: 
.LFB2: 
     .cfi_startproc 
     .cfi_personality 0x0,__gxx_personality_v0 
     pushl %ebp 
     .cfi_def_cfa_offset 8 
     movl %esp, %ebp 
     .cfi_offset 5, -8 
     .cfi_def_cfa_register 5 
     subl $20, %esp 
     leal -4(%ebp), %eax 
     movl %eax, (%esp) 
     call _Z3fooRi 
     leal -4(%ebp), %eax 
     movl %eax, (%esp) 
     call _Z3barPi 
     leave 
     ret 
     .cfi_endproc 
.LFE2: 
     .size _Z7do_testv, .-_Z7do_testv 
     .ident "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3" 
     .section  .note.GNU-stack,"",@progbits 

Come si può vedere, in entrambe le funzioni, la compilatore legge il (stack movl 8(%ebp), %eax), e in entrambe le chiamate il compilatore salva l'indirizzo nello stack (leal -4(%ebp), %eax).

La risposta GMan - Save the Unico ha dato un la dichiarazione C potrebbe essere il problema. Sembra che il problema sia l'interoperabilità tra C e Fortran (almeno quei due compilatori che stai utilizzando).

1

Nessuna differenza.

unsigned & bandiera

è esattamente come se si scrivesse

unsigned * bandiera const

tranne che per gli operatori di accedere ai membri di oggetto ("" e "->" rispettivamente).

Problemi correlati