Skip to content

Der Unterschied zwischen call-by-value und call-by-reference

Byte Order Mark

Es gibt verschiedene Möglichkeiten an eine Funktion bzw. Methode Parameter zu übergeben. Bekannt sind call-by-value (CBV) und call-by-reference (CBR). Ich möchte kurz auf die Unterschiede eingehen und welche Möglichkeiten der Parameterübergabe verschiedene Programmiersprachen bieten.

call-by-value

Bei CBV werden die an eine Funktion übergebenen Daten kopiert. Es besteht dann keine Verbindung mehr zwischen den Daten beim Aufrufer und den Daten in der Funktion. Werden große Objekte auf diese Weise übergeben, so ist das sehr kostspielig in Bezug auf Rechenzeit (der Kopiervorgang) und Speicherplatz (Daten sind mehrmals vorhanden).

call-by-reference

Anstatt die Daten zu kopieren werden Referenzen auf die Daten übergeben. Man kann sich das als Zeiger vorstellen: Beim Aufrufer zeigt eine Variable auf ein bestimmtes Datum, in der Funktion zeigt eine andere Variable auf dasselbe Datum. Methodenaufrufe an einem so übergebenen Objekt arbeiten also auf demselben Objekt, das auch außerhalb sichtbar ist.

Beispiele

Anhand der Programmiersprachen C++, Java und soll das Konzept verdeutlicht werden.

C++

In C++ kann der Programmierer angeben, auf welche Art ein Datum übergeben wird:

void f( int x ){
  x = x * 2;
}

Hier wird das Datum call-by-value übergeben. x enthält eine Kopie des Werts, der durch den Aufrufer übergeben wurde. Eine Änderung von x innerhalb der Methode ist beim Aufrufer nicht sichtbar.

void g( int& x ){
  x = x * 2;
}

Hier wird das Datum per Referenz übergeben. Dafür sorgt der Referenzoperator &. x zeigt nun auf dasselbe Objekt (in diesem Fall einen Integer), auf das auch der Aufrufer zugreift. Mehr noch, x ist dieselbe Referenz, die auch dem Aufrufer zur Verfügung steht. Dadurch ist eine Änderung der Referenz (d.h. ein ‚Umbiegen‘ auf ein anderes Objekt) auch beim Aufrufer sichtbar. Dies ist beachtenswert, weil das in Java und Python nicht der Fall ist. Dazu später mehr.

void h( int* x ){
  (*x) = (*x) * 2;
}

Der vorangehende Code stellt dieselbe Funktionalität wie der Code mit dem Referenzoperator dar, implementiert diese jedoch mit Hilfe von Zeigern. Es ist offensichtlich, dass die Umsetzung mit Zeigern weniger komfortabel ist als die mit Referenzen. Da es den Referenzoperator allerdings nur in C++ und nicht in C gibt, bietet der Einsatz von Zeigern die Möglichkeit ein call-by-reference in C zu realisieren.

Java

Java ist irgendwie ’schwieriger‘ was die Parameterübergabe angeht. Andererseits auch ganz einfach: In Java werden Parameter immer per call-by-value übergeben…

void f( int x ){
  x = x * 2;
}

Da der Wert von x CBV übergeben wurde, bekommt der Aufrufer von einer Veränderung nichts mit.

In Java ist zwischen primitiven Datentypen und Objekttypen zu unterscheiden. Wie sieht es also mit folgendem Code aus?

void g( SomeObject x ){
  x = new SomeObject()
}

Auch hier bekommt der Aufrufer nichts von der Änderung mit. Wieso? Das liegt daran, dass x zwar eine Referenz auf ein Objekt ist, diese Referenz allerdings per call-by-value übergeben wurde. D.h. sie wurde kopiert. Bei der Parameterübergabe hat man also zwei Referenzen, die auf dasselbe Objekt zeigen. Im Gegensatz zu einer Referenz (in zwei Variablen), die auf dasselbe Objekt zeigen, im Falle von C++ und dem Referenzoperator.

Bei einer Neuzuweisung eines Wertes an eine Variable verhält sich Java also bei primitiven Datentypen und Objekttypen gleich. Praktisch. Aber…

void h( SomeObject x ){
  x.doSomethingWithSideEffect()
}

Hier wird eine Methode auf dem Objekt aufgerufen, das von x referenziert wird. Da Java Parameter immer per CBV übergibt, ist die Referenz, die x speichert, eine Kopie. Allerdings zeigt sie auf dasselbe Objekt wie beim Aufrufer. Hat der Methodenaufruf Seiteneffekte auf das Objekt (wir nehmen das mal an), dann können diese Seiteneffekt beim Aufrufer wahrgenommen werden. Natürlich merkt der Aufrufer in nachfolgendem Code wiederum nichts von den Seiteneffekten, da die Referenz vor dem Methodenaufruf ‚entkoppelt‘ wird:

void i( SomeObject x ){
  x = new SomeObject()
  x.doSomethingWithSideEffect()
}

Also: In Java werden Parameter immer per call-by-value übergeben. Auch Referenzen. Bei veränderbaren Objekten sind Veränderungen in der Methode auch beim Aufrufer sichtbar. Bei unveränderbaren Typen, wie beispielsweise primitiven Datentypen und Strings, merkt der Aufrufer nichts. Bei solchen unveränderlichen Typen könnte man eine Veränderung durch einen Wrapper nach außen tragen. Das ist vom Design her allerdings wahrscheinlich keine gute Lösung…

Python

Python verhält sich genau wie Java. Im Prinzip. Aber: In Python wird auf alle Daten mit Referenzen zugegriffen. Auch primitive Datentypen. Und: In Python ist viel einfacher festzustellen, was vor sich geht:

>>> def f(x):
...     x = x * 2
...
>>> s = 5
>>> l = ['o']
>>> f(s), f(l)
(None, None)
>>> s
5
>>> f(s)
>>> s
5
>>> f(l)
>>> l
['o']

Es sollte klar werden, dass auch Python call-by-value einsetzt. Dass Referenzen übergeben werden zeigt die folgende Interaktion mit dem Python-Interpreter:

>>> def g(x):
...     x.append('la')
...
>>> g(l)
>>> l
['o', 'la']

Bei veränderlichen Objekten schlägt sich eine Änderung innerhalb einer Methode also zum Aufrufer durch. Wie zu erwarten. Bei CBV mit Referenzen…

Ich möchte noch auf einen Punkt hinweisen. Das Python-Beispiel zeigt, wie hilfreich, nützlich und vor allem lehrreich das Arbeiten mit einem interaktiven Interpreter sein kann. Eine solche Interaktion liefert unmittelbares Feedback, das didaktisch sehr viel bringt. Und damit zurück zur Parameterübergabe…

{ 20 } Comments

  1. cb | 2007/2/21 at 11:47 | Permalink

    guter Artikel , gut erklärt ! thnx

  2. Markus | 2007/5/13 at 08:48 | Permalink

    Sehr vielen Dank! 🙂 Guter Artikel!

  3. fankmann | 2007/12/7 at 05:06 | Permalink

    Super Erklärung echt Spitze
    hat mir echt weitergeholfen
    Danke

  4. Prosurferin | 2008/1/13 at 12:52 | Permalink

    Jo, ist logisch + sehr gut erklärt!

  5. Noor | 2008/1/18 at 10:08 | Permalink

    Sehr verständlich dargestellt und gute Beispiele. Vielen Dank!

  6. Sebastian | 2008/9/7 at 03:42 | Permalink

    Super Erkärung =) hab grade für meine Informatik Klausur am Dienstag nachgeguckt 😛

  7. Hauser Roman | 2008/9/8 at 11:39 | Permalink

    Ich wollte nur kaffee trinken.. java.. aber hey was ist passiert? call by völiu.. ?
    nun ja.. schönen tag

  8. Michael | 2008/11/13 at 11:32 | Permalink

    Super Artikel!, danke, gruss

  9. sporedjack | 2009/6/8 at 06:41 | Permalink

    Danke, sehr gut erklärt!

  10. Hasan | 2009/6/11 at 05:46 | Permalink

    Hey, vielen Dank. Hat mir weitergeholfen 😉 bye

  11. Luitzifa | 2010/6/20 at 11:03 | Permalink

    und selbst 5 jahre später hilft dieser artikel noch menschen wie mir…
    danke!

  12. buschflitzer | 2010/10/28 at 12:23 | Permalink

    Sehr gut erklärt.
    Toller Artikel, weiter so!!

  13. Horst | 2010/12/22 at 08:33 | Permalink

    Danke, gefällt mir !

  14. Ich | 2011/8/20 at 07:19 | Permalink

    Danke!

  15. David Vielhuber | 2012/5/21 at 09:19 | Permalink

    Der Inhalt dieses Artikels ist nach wie vor relevant, Danke für die Ausarbeitung! Die Architektur von Java hat durchaus ihren Sinn: Für alle anderen Fälle gibt es ja das Schlüsselwort „static“. 

  16. beetle16 | 2013/7/1 at 12:48 | Permalink

    C++, Wikipedia: Once a reference is created, it cannot be later made to reference another object; it cannot be reseated.
    Diese Aussage steht im Widerspruch mit dem Satz „Dadurch ist eine Änderung der Referenz (d.h. ein ‘Umbiegen’ auf ein anderes Objekt) auch beim Aufrufer sichtbar.“ aus diesem Artikel. In Ihrem Beispiel ändern Sie lediglich den Inhalt, auf den die Referenz zeigt (dereferenzieren), während die Referenz selbst nicht umgebogen wird. 
     
    Ganz nebenbei ist dies dasselbe Verhalten, wie es auch Java bei Objekttypen macht: Sie können die Referenz selbst nicht verbiegen für den Aufrufer, aber das referenzierte Objekt.
    Kam mir nur etwas komisch vor. Ansonsten super Artikel.

  17. Steffen | 2013/7/16 at 08:43 | Permalink

    Hallo beetle16,

     

    Ich bin nicht so im C++-Sprech und offensichtlich habe ich ‚Referenz‘ im C++-Umfeld falsch verwendet.

    Aber weil ich mich so gar nicht in C++ reindenken möchte, hier noch ein Link zu der von dir angesprochenen Seite – damit man sich selbst ein Bild machen kann: http://en.wikipedia.org/wiki/Reference_(C%2B%2B)#Uses_of_references

    Wieso das, was dort passiert, nicht ‚reseating‘ ist, versteh ich jetzt auf die Schnelle – wie gesagt – nicht.

     

    Viel Spaß beim selbst-Verstehen!

     

  18. beetle16 | 2013/7/21 at 11:28 | Permalink

    Hallo Steffen,
     
    vielen Dank für deine Antwort.
    Ich glaube, dass das Missverständnis in der Zeile der Zuweisung (auf deiner verlinkten Wikipedia-Seite) in dem Beispiel liegt:
     
    void square(int x, int& result) {
    result = x * x;
    }
     
    Diese Zuweisung der Referenz in C++ ändert nichts an der Adresse, auf die ‚result‘ referiert, sondern nur am dereferenzierten Wert; das ist äquivalent einer set(int value)-Methode eines Objektes. 
    Es findet kein ‚reseating‘ statt. Das selbe Beispiel in Zeigerschreibweise verdeutlicht das:
     
    void square(int x, int* result) {
    *result = x*x;//result wird durch den ‚*‘ vor der Zuweisung dereferenziert
    }
     
    Java hingegen führt bei Zuweisungen von Objekttypen immer ein ‚reseating‘ der Referenz durch (wie C++ es bei Zeigern machen würde). Das Problem ist natürlich, dass bei einem Funktionsaufruf die Referenz ja erst kopiert wird, und nur die kopierte Referenz ‚reseated‘ wird, nicht aber die originale. Das, was in Java passiert, entspräche folgendem C++ Code:
     
    void square(int x, int* result) {
    result = new int(x*x);//new gibt die Adresse des neuen integers zurück
    }
     
    Der Aufrufer kann die Änderung am result-Pointer nicht sehen, da nur die Kopie geändert wurde. Die Sprachen tun mit derselben Syntax verschiedene Dinge.
     
    Viele Grüße, beetle16

  19. Kim Peter | 2015/10/1 at 11:53 | Permalink

    super Erklärung! Ich programmiere hauptsächlich in JAVA, da vergisst man schnell, dass die Parameterübergabe immer als Call by value erfolgt. Denn übergebe ich ein Objekt an eine Funktion und ändere dort dessen Attribute, dann ist das Verhalten so, als würde ich eine Referenz übergeben.

  20. Adolf Hütler | 2016/6/2 at 08:28 | Permalink

    WOLLT IHT DEN TOTALEN KRRRIEEEEGG?? ICH HABE GESTERN EINEN WICHTIGN BEITRAG GELEISTET UND IHR HABT IHN GELÖSCHHT!!!!!

{ 1 } Trackback

  1. […] http://blog.gungfu.de/archives/2005/07/05/der-unterschied-zwischen-call-by-value-und-call-by-referen… […]

Post a Comment

Your email is never published nor shared. Required fields are marked *