Übergabe als Referenz in C.

204

Wenn C die Übergabe einer Variablen als Referenz nicht unterstützt, warum funktioniert dies?

#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;
}

Ausgabe:

$ gcc -std=c99 test.c
$ a.exe
i = 21 
aks
quelle
22
Wo in diesem Code geben Sie die Referenz weiter ?
Atul
15
Es ist zu beachten, dass C keine Referenzübergabe hat, sondern nur mit Zeigern emuliert werden kann.
Einige Programmierer Typ
6
Die korrekte Anweisung lautet "C unterstützt nicht die implizite Übergabe einer Variablen als Referenz". Sie müssen &vor dem Aufrufen der Funktion explizit eine Referenz (mit ) erstellen und diese explizit (mit *) in der Funktion dereferenzieren .
Chris Dodd
2
Ihre Code-Ausgabe ist genau gleich, wenn der Aufruf f(&i);eine Implementierung von Referenzübergabe ist, die nicht nur in C existiert. C Referenzübergabe
EsmaeelE
@Someprogrammerdude Das Übergeben eines Zeigers erfolgt als Referenz. Dies scheint eine dieser Tatsachen zu sein, auf die "versierte" C-Programmierer stolz sind. Als würden sie einen Kick draus machen. "Oh, Sie könnten denken, C hat eine Referenzübergabe, aber nein, es ist eigentlich nur der Wert einer Speicheradresse, die harharhar übergeben wird." Das Übergeben als Referenz bedeutet wörtlich nur das Übergeben der Speicheradresse, in der eine Variable gespeichert ist, und nicht den Wert der Variablen selbst. Dies ist, was C zulässt, und es wird jedes Mal als Referenz übergeben, wenn Sie einen Zeiger übergeben, da ein Zeiger eine Referenz auf einen Speicherort für Variablen ist.
YungGun

Antworten:

315

Weil Sie den Wert des Zeigers an die Methode übergeben und ihn dann dereferenzieren, um die Ganzzahl zu erhalten, auf die verwiesen wird.

Tvanfosson
quelle
4
f (p); -> bedeutet das Wertübergabe? dann dereferenzieren, um die Ganzzahl zu erhalten, auf die gezeigt wird. -> Könnten Sie bitte weitere Erklärungen geben?
Bapi
4
@bapi: Dereferenzieren eines Zeigers bedeutet, "den Wert zu erhalten, auf den sich dieser Zeiger bezieht".
Rauni Lillemets
Was wir für die Methode zum Aufrufen der Funktion aufrufen, die die Adresse der Variablen verwendet, anstatt den Zeiger zu übergeben. Beispiel: func1 (int & a). Ist das nicht ein Referenzanruf? In diesem Fall wird die Referenz wirklich genommen und im Falle eines Zeigers übergeben wir immer noch den Wert, da wir den Zeiger nur als Wert übergeben.
Jon Wheelock
1
Bei der Verwendung von Zeigern ist die Schlüsselfaktor, dass eine Kopie des Zeigers an die Funktion übergeben wird. Die Funktion verwendet dann diesen Zeiger, nicht den ursprünglichen. Dies ist immer noch ein Pass-by-Wert, aber es funktioniert.
Danijel
1
@Danijel Es ist möglich, einen Zeiger, der keine Kopie von irgendetwas ist, an einen Funktionsaufruf zu übergeben. Beispiel: Aufruf der Funktion func: func(&A);Dies würde einen Zeiger auf A an die Funktion übergeben, ohne etwas zu kopieren. Es handelt sich um eine Wertübergabe, aber dieser Wert ist eine Referenz, sodass Sie die Variable A als Referenz übergeben. Kein Kopieren erforderlich. Es ist gültig zu sagen, dass es sich um eine Referenz handelt.
YungGun
124

Dies ist keine Referenzübergabe, sondern eine Wertübergabe, wie andere angegeben haben.

Die C-Sprache wird ausnahmslos als Wert übergeben. Das Übergeben eines Zeigers als Parameter bedeutet nicht, dass Sie als Referenz übergeben werden.

Die Regel lautet wie folgt:

Eine Funktion kann den tatsächlichen Parameterwert nicht ändern.


Versuchen wir, die Unterschiede zwischen Skalar- und Zeigerparametern einer Funktion zu erkennen.

Skalare Variablen

Dieses kurze Programm zeigt die Wertübergabe mithilfe einer skalaren Variablen. paramwird als formaler Parameter bezeichnet und variablebeim Aufrufen der Funktion als tatsächlicher Parameter. Hinweis Inkrementierung paramin der Funktion ändert sich nicht 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;
}

Das Ergebnis ist

I've received value 111
variable=111

Illusion von Referenzübergabe

Wir ändern den Code leicht. paramist jetzt ein Zeiger.

#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;
}

Das Ergebnis ist

I've received value 111
variable=112

Das lässt Sie glauben, dass der Parameter als Referenz übergeben wurde. Es war nicht. Es wurde als Wert übergeben, wobei der Parameterwert eine Adresse ist. Der Wert vom Typ int wurde inkrementiert, und dies ist der Nebeneffekt, der uns glauben lässt, dass es sich um einen Funktionsaufruf mit Referenzübergabe handelt.

Zeiger - Wertübergabe

Wie können wir diese Tatsache zeigen / beweisen? Nun, vielleicht können wir das erste Beispiel für skalare Variablen ausprobieren, aber anstelle von skalar verwenden wir Adressen (Zeiger). Mal sehen, ob das helfen kann.

#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;
}

Das Ergebnis ist, dass die beiden Adressen gleich sind (machen Sie sich keine Sorgen über den genauen Wert).

Beispielergebnis:

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

Meiner Meinung nach beweist dies deutlich, dass Zeiger wertmäßig übergeben werden. Andernfalls ptrwäre NULLnach Funktionsaufruf.

Ely
quelle
69

In C wird die Referenzübergabe simuliert, indem die Adresse einer Variablen (ein Zeiger) übergeben und diese Adresse innerhalb der Funktion dereferenziert wird, um die tatsächliche Variable zu lesen oder zu schreiben. Dies wird als "C-Style-Pass-by-Reference" bezeichnet.

Quelle: www-cs-students.stanford.edu

Tamas Czinege
quelle
50

Weil der obige Code keine Referenz enthält. Die Verwendung von Zeigern (z. B. void func(int* p)) erfolgt nach Adresse. Dies ist eine Referenz in C ++ (funktioniert in C nicht):

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

...
int a;
func(a);
// a is 4 now
Alexander Gessler
quelle
1
Ich mag die Antwort auf die Pass-by-Adresse . Macht mehr Sinn.
Afzaal Ahmad Zeeshan
Adresse und Referenz sind in diesem Zusammenhang synonym. Aber Sie können diese Begriffe verwenden, um die beiden zu unterscheiden, es ist einfach nicht ehrlich zu ihrer ursprünglichen Bedeutung.
YungGun
27

Ihr Beispiel funktioniert, weil Sie die Adresse Ihrer Variablen an eine Funktion übergeben, die ihren Wert mit dem Dereferenzierungsoperator bearbeitet .

Obwohl C keine Referenzdatentypen unterstützt , können Sie die Referenzübergabe simulieren, indem Sie wie in Ihrem Beispiel explizit Zeigerwerte übergeben.

Der C ++ - Referenzdatentyp ist weniger leistungsfähig, wird jedoch als sicherer angesehen als der von C geerbte Zeigertyp. Dies wäre Ihr Beispiel, angepasst an die Verwendung von C ++ - Referenzen :

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

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

  return 0;
}
Daniel Vassallo
quelle
3
In diesem Wikipedia-Artikel geht es um C ++, nicht um C. Referenzen existierten vor C ++ und hängen nicht von der speziellen C ++ - Syntax ab.
1
@ Roger: Guter Punkt ... Ich habe den expliziten Verweis auf C ++ aus meiner Antwort entfernt.
Daniel Vassallo
1
Und dieser neue Artikel besagt, dass "eine Referenz oft als Zeiger bezeichnet wird", was nicht ganz Ihrer Antwort entspricht.
12

Sie übergeben einen Zeiger (Adressposition) nach Wert .

Es ist wie zu sagen: "Hier ist der Ort mit den Daten, die Sie aktualisieren sollen."

antony.trupe
quelle
6

p ist eine Zeigervariable. Sein Wert ist die Adresse von i. Wenn Sie f aufrufen, übergeben Sie den Wert von p, der die Adresse von i ist.

Anssi
quelle
6

Keine Referenzübergabe in C, aber p "bezieht" sich auf i, und Sie übergeben p als Wert.

Pavel Radzivilovsky
quelle
5

In C ist alles Wertübergabe. Die Verwendung von Zeigern gibt uns die Illusion, dass wir als Referenz übergeben, weil sich der Wert der Variablen ändert. Wenn Sie jedoch die Adresse der Zeigervariablen ausdrucken, werden Sie feststellen, dass sie nicht betroffen ist. Eine Kopie des Wertes der Adresse ist in gebenen an die Funktion. Unten sehen Sie einen Ausschnitt, der dies veranschaulicht.

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
Arun Suresh
quelle
4

Weil Sie einen Zeiger (Speicheradresse) auf die Variable p in die Funktion f übergeben. Mit anderen Worten, Sie übergeben einen Zeiger, keine Referenz.

Dave
quelle
4

Kurze Antwort: Ja, C implementiert die Parameterübergabe als Referenz mithilfe von Zeigern.

Bei der Implementierung der Parameterübergabe verwenden Entwickler von Programmiersprachen drei verschiedene Strategien (oder semantische Modelle): Daten in das Unterprogramm übertragen, Daten aus dem Unterprogramm empfangen oder beides. Diese Modelle sind allgemein als In-Modus, Out-Modus und Inout-Modus entsprechend bekannt.

Von Sprachdesignern wurden mehrere Modelle entwickelt, um diese drei Strategien zur Übergabe elementarer Parameter zu implementieren:

Pass-by-Value (Semantik im Modus) Pass-by-Result (Semantik im Out-Modus) Pass-by-Value-Ergebnis (Semantik im Inout-Modus) Pass-by-Reference (Semantik im Inout-Modus) Pass-by-Name (Inout-Modus) Semantik)

Pass-by-Reference ist die zweite Technik zum Übergeben von Parametern im Inout-Modus. Anstatt Daten zwischen der Hauptroutine und dem Unterprogramm hin und her zu kopieren, sendet das Laufzeitsystem einen direkten Zugriffspfad auf die Daten für das Unterprogramm. Bei dieser Strategie hat das Unterprogramm direkten Zugriff auf die Daten, wobei die Daten effektiv mit der Hauptroutine geteilt werden. Der Hauptvorteil dieser Technik besteht darin, dass sie zeitlich und räumlich absolut effizient ist, da kein Speicherplatz dupliziert werden muss und keine Datenkopiervorgänge erforderlich sind.

Die Implementierung der Parameterübergabe in C: C implementiert die Semantik der Wertübergabe und der Referenzübergabe (Inout-Modus) unter Verwendung von Zeigern als Parameter. Der Zeiger wird an das Unterprogramm gesendet und es werden überhaupt keine tatsächlichen Daten kopiert. Da ein Zeiger jedoch ein Zugriffspfad auf die Daten der Hauptroutine ist, kann das Unterprogramm die Daten in der Hauptroutine ändern. C hat diese Methode von ALGOL68 übernommen.

Implementierung der Parameterübergabe in C ++: C ++ implementiert auch die Semantik der Referenzübergabe (Inout-Modus) mithilfe von Zeigern und einer speziellen Art von Zeigern, die als Referenztyp bezeichnet wird. Referenztypzeiger werden implizit innerhalb des Unterprogramms dereferenziert, aber ihre Semantik wird auch als Referenz übergeben.

Das Schlüsselkonzept hier ist also, dass die Referenzübergabe einen Zugriffspfad auf die Daten implementiert, anstatt die Daten in das Unterprogramm zu kopieren. Datenzugriffspfade können explizit dereferenzierte Zeiger oder automatisch dereferenzierte Zeiger (Referenztyp) sein.

Weitere Informationen finden Sie im Buch Concepts of Programming Languages ​​von Robert Sebesta, 10. Aufl., Kapitel 9.

Francisco Zapata
quelle
3

Sie übergeben kein int als Referenz, sondern einen Zeiger auf ein int als Wert. Unterschiedliche Syntax, gleiche Bedeutung.

xan
quelle
1
+1 "Unterschiedliche Syntax, gleiche Bedeutung." .. Also das Gleiche, denn die Bedeutung ist wichtiger als die Syntax.
Nicht wahr. Wenn Sie void func(int* ptr){ *ptr=111; int newValue=500; ptr = &newvalue }mit aufrufen int main(){ int value=0; func(&value); printf("%i\n",value); return 0; }, wird 111 anstelle von 500 ausgegeben. Wenn Sie als Referenz übergeben, sollte 500 ausgegeben werden. C unterstützt die Übergabe von Parametern als Referenz nicht.
Konfle Dolex
@Konfle, wenn Sie syntaktisch als Referenz übergeben, wird ptr = &newvaluenicht zugelassen. Unabhängig vom Unterschied denke ich, dass Sie darauf hinweisen, dass "gleiche Bedeutung" nicht genau wahr ist, weil Sie auch zusätzliche Funktionen in C haben (die Möglichkeit, die "Referenz" selbst neu zuzuweisen).
Xan
Wir schreiben niemals so etwas, ptr=&newvaluewenn es als Referenz übergeben wird. Stattdessen schreiben wir ptr=newvalueHier ist ein Beispiel in C ++: void func(int& ptr){ ptr=111; int newValue=500; ptr = newValue; }Der Wert des an func () übergebenen Parameters wird 500.
Konfle Dolex
Im Fall meines obigen Kommentars ist es sinnlos, param als Referenz zu übergeben. Wenn der Parameter jedoch ein Objekt anstelle von POD ist, hat dies einen signifikanten Unterschied, da jede Änderung param = new Class()innerhalb einer Funktion keine Auswirkung auf den Aufrufer hat, wenn sie als Wert (Zeiger) übergeben wird. Wenn paramals Referenz übergeben wird, sind die Änderungen für den Anrufer sichtbar.
Konfle Dolex
3

In C verwenden Sie zum Übergeben als Referenz den Operator "Adresse von", &der für eine Variable verwendet werden soll. In Ihrem Fall pmüssen Sie dem Operator "Adresse von" jedoch kein Präfix voranstellen , da Sie die Zeigervariable verwendet haben . Es wäre wahr gewesen, wenn Sie &ials Parameter verwendet hätten : f(&i).

Sie können dies auch hinzufügen, um zu dereferenzieren pund zu sehen, wie dieser Wert übereinstimmt i:

printf("p=%d \n",*p);
t0mm13b
quelle
Warum mussten Sie den gesamten Code (einschließlich dieses Kommentarblocks) wiederholen, um ihm mitzuteilen, dass er einen Ausdruck hinzufügen soll?
@Neil: Dieser Fehler wurde durch @ Williams Bearbeitung eingeführt. Ich werde ihn jetzt umkehren. Und jetzt ist es offensichtlich, dass tommieb nur größtenteils richtig ist: Sie können & auf jedes Objekt anwenden, nicht nur auf Variablen.
2

Zeiger und Referenzen sind zwei verschiedene Thigngs.

Ein paar Dinge, die ich nicht erwähnt habe.

Ein Zeiger ist die Adresse von etwas. Ein Zeiger kann wie jede andere Variable gespeichert und kopiert werden. Es hat also eine Größe.

Eine Referenz sollte als ALIAS von etwas angesehen werden. Es hat keine Größe und kann nicht gespeichert werden. Es muss sich auf etwas beziehen, dh. es kann nicht null sein oder geändert werden. Nun, manchmal muss der Compiler die Referenz als Zeiger speichern, aber das ist ein Implementierungsdetail.

Bei Referenzen treten keine Probleme mit Zeigern auf, wie z. B. die Behandlung von Eigentumsrechten, die Nullprüfung und die De-Referenzierung bei Verwendung.

user2187033
quelle
1

'Als Referenz übergeben' (mithilfe von Zeigern) war von Anfang an in C. Warum denkst du, ist es nicht?

Maurits Rijk
quelle
7
Weil es technisch nicht als Referenz dient.
Mehrdad Afshari
7
Das Übergeben eines Zeigerwerts ist nicht dasselbe wie das Übergeben einer Referenz. Das Aktualisieren des Werts von j( nicht *j ) in f()hat keine Auswirkung auf iin main().
John Bode
6
Es ist semantisch dasselbe wie das Übergeben als Referenz, und das ist gut genug, um zu sagen, dass es als Referenz übergeben wird. Der C-Standard verwendet zwar nicht den Begriff "Referenz", aber das überrascht mich weder noch ist es ein Problem. Wir sprechen auch kein Standardese auf SO, obwohl wir uns möglicherweise auf den Standard beziehen, sonst würden wir niemanden sehen, der über r-Werte spricht (der C-Standard verwendet den Begriff nicht).
4
@ Jim: Danke, dass du uns gesagt hast, dass du Johns Kommentar positiv bewertet hast.
1

Ich denke, C unterstützt tatsächlich die Referenzübergabe.

Die meisten Sprachen erfordern syntaktischen Zucker als Referenz anstelle von Wert. (C ++ erfordert zum Beispiel & in der Parameterdeklaration).

C benötigt dafür auch syntaktischen Zucker. Es ist * in der Parametertypdeklaration und & im Argument. Also * und & ist die C-Syntax für die Referenzübergabe.

Man könnte nun argumentieren, dass eine echte Referenzübergabe nur eine Syntax für die Parameterdeklaration erfordern sollte, nicht für die Argumentationsseite.

Aber jetzt kommt C # , das tut Unterstützung durch Bezugnahme Gang und erfordert syntaktischen Zucker auf beiden Parameter und Argumente Seiten.

Das Argument, dass C keine By-Ref-Übergabe hat, führt dazu, dass die syntaktischen Elemente, die es ausdrücken, die zugrunde liegende technische Implementierung aufweisen, überhaupt kein Argument, da dies mehr oder weniger für alle Implementierungen gilt.

Das einzige verbleibende Argument ist, dass das Übergeben von ref in C kein monolithisches Merkmal ist, sondern zwei vorhandene Merkmale kombiniert. (Nehmen Sie ref of argument by &, erwarten Sie, dass ref by * eingibt.) Für C # sind beispielsweise zwei syntaktische Elemente erforderlich, die jedoch nicht ohne einander verwendet werden können.

Dies ist offensichtlich ein gefährliches Argument, da viele andere Funktionen in Sprachen aus anderen Funktionen bestehen. (wie String-Unterstützung in C ++)

Johannes
quelle
1

Was Sie tun, ist Wert übergeben, nicht als Referenz übergeben. Weil Sie den Wert einer Variablen 'p' an die Funktion 'f' senden (hauptsächlich als f (p);)

Das gleiche Programm in C mit Referenzübergabe sieht aus wie (!!! Dieses Programm gibt 2 Fehler aus, da Referenzübergabe in C nicht unterstützt wird.)

#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;
}

Ausgabe:-

3:12: Fehler: erwartet ';', ',' oder ')' vor '&' Token
             void f (int & j);
                        ^
9: 3: Warnung: implizite Deklaration der Funktion 'f'
               Fa);
               ^
Sunay
quelle