Quando un oggetto è passato a una funzione?

1

Cercando di indicare chiaramente la semantica di una chiamata di funzione.

Nel chiamare una funzione, gli argomenti passati alla funzione sono quelli che il codice chiamante inizialmente o quelli che la funzione riceve ?

Con il codice come sotto, il codice chiamante in bar() chiama foo() due volte. Questa prima volta con int 2 , quindi ...

1) Funzione foo() è chiamata con double 3.1 , x ha il valore convertito di int 3 ,

o

2) Il valore double 3.1 è convertito in int 3 e la funzione foo() è chiamata,

o

3) Qualcos'altro.

IOWs, è la conversione di double 3.1 in int 3 parte della chiamata di una funzione (la conversione non avverrà senza la chiamata di funzione) o è un'attività precedente (si consideri parte del codice chiamante) prima della chiamata di funzione?

void foo(int x);

void bar() {
  foo(2);
  foo(3.1);
}

Questa query è principalmente C , tuttavia i pensieri agnostici del linguaggio sono apprezzati. Non è necessaria una risposta rapida.

[Modifica]

Nota: questa non è una questione di come le piattaforme creano il binario / eseguibile per implementare il codice del programma - solo per il codice / lingua.

[Modifica 2] @ Erik Eidt un commento utile ha fornito parole migliori da usare (almeno per C) per questa domanda.

Forse una domanda più succinta sarebbe, dal punto di vista della lingua : "Le funzioni sono chiamate con" argomenti reali "o" parametri formali "?

C11 3.3 argomenti attuali e 3.16 1 parametro formale.

    
posta chux 28.08.2015 - 20:58
fonte

4 risposte

1

Mi aspetto che in ogni implementazione di C, la conversione avvenga prima. Lo psuedo-assembly per bar :

store 2 in argument-register-0
call foo
store 3.1 in temporary-register-0
convert-to-int temporary-register-0 into temporary-register-1
store temporary-register-1 in argument-register-0
call foo

Il tuo ottimizzatore è probabilmente abbastanza intelligente da eliminare temporary-register0 e / o temporary-register-1, e diverse versioni di assembly potrebbero aver bisogno di trucchi extra per gestire il floating point, ma i passaggi principali sono generalmente gli stessi.

    
risposta data 28.08.2015 - 21:21
fonte
0

In C ++ hai overloading di funzioni , a cui viene assegnata una serie di funzioni con lo stesso nome ma diversi parametri di input (il valore di ritorno non è considerato) il compilatore deve cercare la scelta migliore, prendendo in considerazione le conversioni implicite e la risoluzione dello spazio dei nomi.

Quindi funzionalmente il compilatore trova una chiamata di funzione con un dato input, e poi guarda se qualche sovraccarico si adatta al conto.

Potrebbero esserci più di un possibile sovraccarico idoneo, quindi cerca di trovare il migliore che viene poi chiamato.

In questo senso la conversione viene eseguita prima di chiamare, poiché inizialmente non si sa nemmeno quale funzione chiamare.

    
risposta data 29.08.2015 - 17:29
fonte
0

La conversione viene sempre eseguita dal codice chiamante, non dalla funzione chiamata (in particolare perché i tipi ottengono cancellati in fase di runtime : l'implementazione non conosce i tipi in fase di esecuzione, solo in fase di compilazione). Una funzione C che è dichiarata (almeno quelli che non sono variadici, ad esempio senza ... che termina la lista di tipi di argomenti, come printf ) ha una firma ben definita, data dal suo prototipo.

In pratica, ogni funzione chiamata dovrebbe avere un prototipo in questi giorni, spesso fornita in qualche file di intestazione.

Questo è coperto da §6.5.2.2 Chiamate di funzione dello standard (citando il n1570 bozza dello standard C11, paragrafo 7):

If the expression that denotes the called function has a type that does include a prototype, the arguments are implicitly converted, as if by assignment, to the types of the corresponding parameters, taking the type of each parameter to be the unqualified version of its declared type.

Quindi applica il tuo caso 2

In pratica, in molte implementazioni dello standard C, il ABI documento specifica convenzioni di segnalazione . Leggi come esempio SVR4 ABI per x86-64 . Spesso, gli argomenti (almeno i primi) vengono passati nei registri e vengono utilizzati registri diversi per int formals e per double ones. Dato che hai dato un prototipo per foo il suo argomento int deve essere passato dal registro %rdi (e %xmm0 verrebbe usato per passare un argomento formale float ) quindi il codice chiamante nella funzione del chiamante caricarebbe il registro intero (magari convertendo un valore float).

BTW, nel tuo esempio foo(3.1) la conversione in 3 è probabile che sia ottimizzata dal compilatore a compile time ( folding costante ), quindi il compilatore molto probabilmente emetterebbe lo stesso codice assembler di foo(3) dato che hai dichiarato con un prototipo void foo(int x); prima della chiamata.

Se usi GCC dovresti compilare qualche semplice codice di esempio nel file foo.c con gcc -fverbose-asm -Wall -O -S foo.c e guardare nel file assembler generato foo.s

Ci sono alcuni linguaggi (come Common Lisp, Scheme, Perl, Python, Ruby) con digitazione dinamica . Quindi, ogni valore conosce il suo tipo (è spesso rappresentato in macchina come un aggregato o una struttura con alcuni dei campi che danno il tipo) e la tua domanda non ha alcun senso (potresti capire che queste implementazioni linguistiche gestiscono solo un tipo di valore , che sarebbe un unione taggata ).

    
risposta data 29.08.2015 - 17:18
fonte
0

Considera una lingua L0 che non fa conversioni implicite come double a int , "promozioni di argomenti predefinite", conversioni di array a "indirizzo del primo oggetto", ecc. Il codice come foo(int); double x; foo(x); è quindi un errore di compilazione .

double x;
foo(x); // Bad L0 code

L0 non è pertinente a questa domanda a parte il fatto che mostra se il codice L0 voleva chiamare foo(int) con double , eseguirebbe esplicitamente la conversione.

double x;
foo(double_to_int(x));  // L0: Explicit conversion before calling 'foo()'.

Considera una lingua L1 che esegue tutte le conversioni implicite sopra. Quando il codice ha una chiamata alla funzione foo(int) e viene passato un parametro double , viene creata una funzione che accetta double , la converte in int e quindi esegue il codice di foo(int) . Il compilatore L1 ha almeno 4 scelte: creare 2 funzioni indipendenti, 2 funzioni in cui una chiama l'altra, rende una funzione con più punti di ingresso: una per int argomenti e un'altra per double , o una funzione con informazioni di tipo aggiunte nell'argomento passato.

double x;
foo(x); // Good L1 code, no conversion done in the calling code.

C non è certamente LO con le conversioni implicite di C. C potrebbe essere L1 in quanto vi è poco nella specifica C che definisce come organizzare il suo passaggio di argomenti - il suo ABI . Come @ Erik Eidt ha fornito commenti utili ad alcune definizioni chiave, un linguaggio come C (e L1), che ha una potenziale differenza tra argomenti reali e parametri formali.

Questo confonde il confine tra il codice chiamante e la funzione chiamata.

Alla fine, con C, è la definizione di parametro formale che dice "acquisisce un valore su voce alla funzione" che implica che la funzione non è ancora chiamata fino a questo punto.

Ref

C11 3.3
argomento effettivo
espressione nella lista separata da virgole delimitata dalle parentesi in una espressione di chiamata di funzione, o una sequenza di token di preelaborazione nella lista separata da virgole delimitata dalle parentesi in una chiamata di macro simile a una funzione

C11 3.16
parametro formale
oggetto dichiarato come parte di una dichiarazione di funzione o definizione che acquisisce un valore sulla voce della funzione, o un identificatore dalla lista separata da virgole delimitata dalle parentesi che seguono immediatamente il nome della macro in una definizione di macro simile alla funzione

    
risposta data 01.09.2015 - 01:47
fonte

Leggi altre domande sui tag