Passando per riferimento in C


204

Se C non supporta il passaggio di una variabile per riferimento, perché funziona?

#include <stdio.h>

void f(int *j) {
  (*j)++;
}

int main() {
  int i = 20;
  int *p = &i;
  f(p);
  printf("i = %d\n", i);

  return 0;
}

Produzione:

$ gcc -std=c99 test.c
$ a.exe
i = 21 

22
Dove in questo codice stai passando riferimento ?
Atul,

15
Va notato che C non ha un passaggio per riferimento, può solo essere emulato usando i puntatori.
Qualche programmatore, amico,

6
L'istruzione corretta è "C non supporta il passaggio implicito di una variabile per riferimento" - è necessario creare esplicitamente un riferimento (con &) prima di chiamare la funzione e farne esplicitamente riferimento (con *) nella funzione.
Chris Dodd,

2
L'output del tuo codice equivale esattamente a quando call f(&i);this è un'implementazione di pass by reference, che non esiste puramente in C. C pass by reference
EsmaeelE

@Someprogrammerdude Il passaggio di un puntatore sta passando per riferimento. Questo sembra essere uno di quei fatti su cui i programmatori C "esperti" sono orgogliosi. Come se ne prendessero un calcio. "Oh, potresti PENSARE che C abbia un riferimento pass-by, ma no, in realtà è solo il valore di un indirizzo di memoria che viene passato harharhar". Passare letteralmente per riferimento significa semplicemente passare l'indirizzo di memoria del punto in cui è memorizzata una variabile anziché il valore della variabile stessa. Questo è ciò che consente C ed è pass-by-reference ogni volta che si passa un puntatore, poiché un puntatore è un riferimento a una posizione di memoria delle variabili.
YungGun

Risposte:


315

Perché stai passando il valore del puntatore al metodo e poi dereferenziandolo per ottenere il numero intero a cui è puntato.


4
f (p); -> significa passare per valore? quindi dereferenziandolo per ottenere il numero intero indicato. -> potresti per favore dare ulteriori spiegazioni.
bapi,

4
@bapi, dereferenziare un puntatore significa "ottenere il valore a cui questo puntatore fa riferimento".
Rauni Lillemets,

Quello che chiamiamo per il metodo di chiamare la funzione che prende l'indirizzo della variabile invece di passare il puntatore. Esempio: func1 (int & a). Non è una chiamata per riferimento? In questo caso il riferimento è veramente preso e nel caso del puntatore, stiamo ancora passando per valore perché stiamo passando il puntatore solo per valore.
Jon Wheelock,

1
Quando si usano i puntatori, il fatto chiave è che la copia del puntatore viene passata nella funzione. La funzione utilizza quindi quel puntatore, non quello originale. Questo è ancora pass-by-value, ma funziona.
Danijel,

1
@Danijel È possibile passare un puntatore che non è una copia di nulla a una chiamata di funzione. Ad esempio, chiamando la funzione func: func(&A);questo passerebbe un puntatore ad A alla funzione senza copiare nulla. È pass-by-value, ma quel valore è un riferimento, quindi si sta 'passando per riferimento` la variabile A. Nessuna copia richiesta. È valido dire che è pass-by-reference.
YungGun,

124

Questo non è pass-by-reference, cioè pass-by-value come affermato da altri.

Il linguaggio C è pass-by-value senza eccezioni. Passare un puntatore come parametro non significa pass-by-reference.

La regola è la seguente:

Una funzione non è in grado di modificare il valore dei parametri effettivi.


Proviamo a vedere le differenze tra i parametri scalare e puntatore di una funzione.

Variabili scalari

Questo breve programma mostra il valore per passaggio usando una variabile scalare. paramviene chiamato parametro formale e variableall'invocazione della funzione viene chiamato parametro effettivo. L'incremento della nota paramnella funzione non cambia variable.

#include <stdio.h>

void function(int param) {
    printf("I've received value %d\n", param);
    param++;
}

int main(void) {
    int variable = 111;

    function(variable);
    printf("variable %d\m", variable);
    return 0;
}

Il risultato è

I've received value 111
variable=111

Illusione del pass-by-reference

Modifichiamo leggermente il codice. paramè un puntatore ora.

#include <stdio.h>

void function2(int *param) {
    printf("I've received value %d\n", *param);
    (*param)++;
}

int main(void) {
    int variable = 111;

    function2(&variable);
    printf("variable %d\n", variable);
    return 0;
}

Il risultato è

I've received value 111
variable=112

Questo ti fa credere che il parametro sia stato passato per riferimento. Non era. È stato passato per valore, il valore param è un indirizzo. Il valore del tipo int è stato incrementato e questo è l'effetto collaterale che ci fa pensare che fosse una chiamata di funzione pass-by-reference.

Puntatori - passati per valore

Come possiamo dimostrare / dimostrare questo fatto? Bene, forse possiamo provare il primo esempio di variabili scalari, ma invece di scalari usiamo gli indirizzi (puntatori). Vediamo se questo può aiutare.

#include <stdio.h>

void function2(int *param) {
    printf("param's address %d\n", param);
    param = NULL;
}

int main(void) {
    int variable = 111;
    int *ptr = &variable;

    function2(ptr);
    printf("ptr's address %d\n", ptr);
    return 0;
}

Il risultato sarà che i due indirizzi sono uguali (non preoccuparti del valore esatto).

Risultato di esempio:

param's address -1846583468
ptr's address -1846583468

A mio avviso, ciò dimostra chiaramente che i puntatori sono passati per valore. Altrimenti ptrsarebbe NULLdopo l'invocazione della funzione.


69

In C, il passaggio per riferimento viene simulato passando l'indirizzo di una variabile (un puntatore) e dereferenziando quell'indirizzo all'interno della funzione per leggere o scrivere la variabile effettiva. Questo sarà indicato come "pass-by-reference in stile C."

Fonte: www-cs-students.stanford.edu


50

Perché non esiste un riferimento pass-by nel codice sopra. L'uso di puntatori (come void func(int* p)) è pass-by-address. Questo è pass-by-reference in C ++ (non funzionerà in C):

void func(int& ref) {ref = 4;}

...
int a;
func(a);
// a is 4 now

1
Mi piace la risposta per indirizzo . Ha più senso.
Afzaal Ahmad Zeeshan,

Indirizzo e riferimento sono sinonimi in questo contesto. Ma puoi usare questi termini per differenziare i due, semplicemente non è onesto con il loro significato originale.
YungGun

27

Il tuo esempio funziona perché stai passando l'indirizzo della tua variabile a una funzione che manipola il suo valore con l' operatore di dereference .

Sebbene C non supporti i tipi di dati di riferimento , è comunque possibile simulare il passaggio per riferimento passando esplicitamente i valori del puntatore, come nell'esempio.

Il tipo di dati di riferimento C ++ è meno potente ma considerato più sicuro del tipo di puntatore ereditato da C. Questo sarebbe il tuo esempio, adattato per usare riferimenti C ++ :

void f(int &j) {
  j++;
}

int main() {
  int i = 20;
  f(i);
  printf("i = %d\n", i);

  return 0;
}

3
Quell'articolo di Wikipedia riguarda C ++, non C. I riferimenti esistevano prima del C ++ e non dipendono dalla speciale sintassi C ++ esistente.

1
@Roger: buon punto ... ho rimosso il riferimento esplicito al C ++ dalla mia risposta.
Daniel Vassallo,

1
E quel nuovo articolo dice "un riferimento è spesso chiamato un puntatore" che non è proprio quello che dice la tua risposta.

12

Stai passando un puntatore (indirizzo) in base al valore .

È come dire "ecco il posto con i dati che voglio che tu aggiorni".


6

p è una variabile puntatore. Il suo valore è l'indirizzo di i. Quando chiami f, passi il valore di p, che è l'indirizzo di i.



5

In C tutto è pass-by-value. L'uso dei puntatori ci dà l'illusione che stiamo passando per riferimento perché il valore della variabile cambia. Tuttavia, se dovessi stampare l'indirizzo della variabile puntatore, vedrai che non viene influenzato. Una copia del valore dell'indirizzo viene passata alla funzione. Di seguito è riportato un frammento che illustra questo.

void add_number(int *a) {
    *a = *a + 2;
}

int main(int argc, char *argv[]) {
   int a = 2;

   printf("before pass by reference, a == %i\n", a);
   add_number(&a);
   printf("after  pass by reference, a == %i\n", a);

   printf("before pass by reference, a == %p\n", &a);
   add_number(&a);
   printf("after  pass by reference, a == %p\n", &a);

}

before pass by reference, a == 2
after  pass by reference, a == 4
before pass by reference, a == 0x7fff5cf417ec
after  pass by reference, a == 0x7fff5cf417ec

4

Perché stai passando un puntatore (indirizzo di memoria) alla variabile p nella funzione f. In altre parole, stai passando un puntatore, non un riferimento.


4

Risposta breve: Sì, C implementa il passaggio dei parametri per riferimento usando i puntatori.

Durante l'implementazione del passaggio dei parametri, i progettisti di linguaggi di programmazione usano tre diverse strategie (o modelli semantici): trasferire i dati nel sottoprogramma, ricevere i dati dal sottoprogramma o fare entrambi. Questi modelli sono comunemente noti come in modalità, modalità out e modalità out, di conseguenza.

Numerosi modelli sono stati ideati dai progettisti del linguaggio per implementare queste tre strategie elementari di passaggio dei parametri:

Passa per valore (semantica in modalità) Passa per risultato (semantica in modalità out) Passa per valore-risultato (semantica in modalità inout) Passa per riferimento (semantica in modalità inout) Passa per nome (modalità inout semantica)

Pass-by-reference è la seconda tecnica per il passaggio dei parametri inout-mode. Invece di copiare i dati avanti e indietro tra la routine principale e il sottoprogramma, il sistema di runtime invia un percorso di accesso diretto ai dati per il sottoprogramma. In questa strategia il sottoprogramma ha accesso diretto ai dati condividendo efficacemente i dati con la routine principale. Il vantaggio principale di questa tecnica è che è assolutamente efficiente nel tempo e nello spazio perché non è necessario duplicare lo spazio e non ci sono operazioni di copia dei dati.

Il parametro che passa l'implementazione in C: C implementa la semantica pass-by-value e pass-by-reference (inout mode) usando i puntatori come parametri. Il puntatore viene inviato al sottoprogramma e nessun dato reale viene copiato. Tuttavia, poiché un puntatore è un percorso di accesso ai dati della routine principale, il sottoprogramma può modificare i dati nella routine principale. C ha adottato questo metodo da ALGOL68.

I parametri che passano l'implementazione in C ++: C ++ implementano anche la semantica pass-by-reference (inout mode) usando i puntatori e usando anche un tipo speciale di puntatore, chiamato tipo di riferimento. I puntatori del tipo di riferimento sono implicitamente dereferenziati all'interno del sottoprogramma, ma anche la loro semantica è pass-by-reference.

Quindi il concetto chiave qui è che il riferimento pass-by implementa un percorso di accesso ai dati invece di copiarli nel sottoprogramma. I percorsi di accesso ai dati possono essere puntatori con riferimento esplicito o puntatori con riferimento automatico (tipo di riferimento).

Per maggiori informazioni, consultare il libro Concetti sui linguaggi di programmazione di Robert Sebesta, decima edizione, capitolo 9.


3

Non stai passando un int per riferimento, stai passando un puntatore a un int per valore. Sintassi diversa, stesso significato.


1
+1 "Sintassi diversa, stesso significato." .. Quindi la stessa cosa, poiché il significato conta più della sintassi.

Non vero. Chiamando void func(int* ptr){ *ptr=111; int newValue=500; ptr = &newvalue }con int main(){ int value=0; func(&value); printf("%i\n",value); return 0; }, stampa 111 anziché 500. Se si passa per riferimento, dovrebbe stampare 500. C non supporta il passaggio di parametri per riferimento.
Konfle Dolex,

@Konfle, se si passa sintatticamente per riferimento, ptr = &newvaluenon è consentito. Indipendentemente dalla differenza, penso che stai sottolineando che lo "stesso significato" non è esattamente vero perché hai anche funzionalità extra in C (la possibilità di riassegnare il "riferimento" stesso).
xan,

Non scriviamo mai qualcosa come ptr=&newvaluese fosse passato per riferimento. Invece, scriviamo ptr=newvalueEcco un esempio in C ++: void func(int& ptr){ ptr=111; int newValue=500; ptr = newValue; }Il valore del parametro passato in func () diventerà 500.
Konfle Dolex,

Nel caso del mio commento sopra, è inutile passare param per riferimento. Tuttavia, se il parametro è un oggetto anziché POD, ciò farà una differenza significativa perché qualsiasi modifica successiva a param = new Class()una funzione non avrebbe alcun effetto per il chiamante se viene passato per valore (puntatore). Se paramviene passato per riferimento, le modifiche sarebbero visibili per il chiamante.
Konfle Dolex,

3

In C, per passare per riferimento usi l'operatore address-of &che dovrebbe essere usato contro una variabile, ma nel tuo caso, dato che hai usato la variabile pointer p, non hai bisogno di prefissarlo con l'operatore address-of. Sarebbe stato vero se si è utilizzato &icome parametro: f(&i).

Puoi anche aggiungere questo, a dereference pe vedere come quel valore corrisponde i:

printf("p=%d \n",*p);

Perché hai sentito la necessità di ripetere tutto il codice (incluso quel blocco di commenti ..) per dirgli che avrebbe dovuto aggiungere una stampa?

@Neil: quell'errore è stato introdotto dalla modifica di @ William, lo annullerò ora. E ora è evidente che tommieb ha ragione soprattutto: puoi applicare e su qualsiasi oggetto, non solo sulle variabili.

2

puntatori e riferimenti sono due diversi thigngs.

Un paio di cose che non ho visto menzionate.

Un puntatore è l'indirizzo di qualcosa. Un puntatore può essere archiviato e copiato come qualsiasi altra variabile. Ha quindi una dimensione.

Un riferimento dovrebbe essere visto come un ALIAS di qualcosa. Non ha una dimensione e non può essere memorizzato. DEVE fare riferimento a qualcosa, ad es. non può essere nullo o modificato. Bene, a volte il compilatore deve memorizzare il riferimento come puntatore, ma questo è un dettaglio di implementazione.

Con i riferimenti non si hanno problemi con i puntatori, come la gestione della proprietà, il controllo nullo, la de-referenziazione durante l'uso.


1

'Passa per riferimento' (usando i puntatori) è stato in C dall'inizio. Perché pensi che non lo sia?


7
Perché tecnicamente non passa per riferimento.
Mehrdad Afshari,

7
Passare un valore del puntatore non è lo stesso del pass-by-reference. L'aggiornamento del valore di j( not *j ) in f()non ha alcun effetto su iin main().
John Bode,

6
È semanticamente la stessa cosa del passaggio per riferimento, ed è abbastanza buono per dire che sta passando per riferimento. È vero, lo standard C non usa il termine "riferimento", ma ciò non mi sorprende né è un problema. Inoltre, non stiamo parlando di standardese su SO, anche se possiamo fare riferimento allo standard, altrimenti non vedremmo nessuno parlare di valori (lo standard C non usa il termine).

4
@Jim: Grazie per averci detto che hai votato a favore del commento di John.

1

Penso che C in effetti supporti il ​​passaggio per riferimento.

Molte lingue richiedono che lo zucchero sintattico passi per riferimento anziché per valore. (Ad esempio C ++ richiede e nella dichiarazione dei parametri).

C richiede anche zucchero sintattico per questo. È * nella dichiarazione del tipo di parametro e & sull'argomento. Quindi * e & è la sintassi C per pass per riferimento.

Si potrebbe ora sostenere che il passaggio reale per riferimento dovrebbe richiedere solo la sintassi sulla dichiarazione del parametro, non sul lato argomento.

Ma ora arriva C #, che fa il supporto da accenno e richiede zucchero sintattico su entrambi i lati dei parametri e argomenti.

L'argomentazione secondo cui C non ha un passaggio di by-ref fa sì che gli elementi sintattici che lo esprimono mostrino l'implementazione tecnica sottostante non è affatto un argomento, poiché ciò si applica più o meno a tutte le implementazioni.

L'unico argomento rimasto è che passare per ref in C non è una caratteristica monolitica ma combina due caratteristiche esistenti. (Prendi ref dell'argomento con &, aspetta che ref digiti per *. C # per esempio richiede due elementi sintattici, ma non possono essere usati l'uno senza l'altro.

Questo è ovviamente un argomento pericoloso, poiché molte altre funzionalità nelle lingue sono composte da altre funzionalità. (come il supporto delle stringhe in C ++)


1

Quello che stai facendo è passare per valore non passare per riferimento. Perché stai inviando il valore di una variabile 'p' alla funzione 'f' (in principale come f (p);)

Lo stesso programma in C con pass per riferimento apparirà, (!!! questo programma fornisce 2 errori poiché pass per riferimento non è supportato in C)

#include <stdio.h>

void f(int &j) {    //j is reference variable to i same as int &j = i
  j++;
}

int main() {
  int i = 20;
  f(i);
  printf("i = %d\n", i);

  return 0;
}

Produzione:-

3:12: errore: previsto ';', ',' o ')' prima del token '&'
             void f (int & j);
                        ^
9: 3: avviso: dichiarazione implicita della funzione 'f'
               fa);
               ^
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.