Warum werden so viele Sprachen als Wert übergeben?

36

Sogar Sprachen, in denen Sie explizite Zeiger-Manipulationen wie C durchführen, werden immer als Wert übergeben (Sie können sie als Referenz übergeben, aber das ist nicht das Standardverhalten).

Was ist der Vorteil davon, warum werden so viele Sprachen von Werten und warum werden andere als Referenz weitergegeben ? (Ich verstehe, dass Haskell als Referenz übergeben wird, obwohl ich nicht sicher bin).

Mein Code hat keine Fehler
quelle
5
void acceptEntireProgrammingLanguageByValue(C++);
Thomas Eding
4
Es könnte schlimmer sein. Einige alte Sprachen dürfen auch beim Namen genannt werden
hugomg 19.06.12
25
Tatsächlich kann man in C nicht als Referenz übergeben. Sie können einen Zeiger als Wert übergeben , was der Referenzübergabe sehr ähnlich ist, jedoch nicht dasselbe. In C ++ können Sie jedoch als Referenz übergeben.
Mason Wheeler
@ Mason Wheeler können Sie mehr ausarbeiten oder einen Link hinzufügen, weil mir Ihre Aussage nicht klar ist, da ich kein C / C ++ - Guru bin, sondern nur ein normaler Programmierer, danke
Betlista
1
@Betlista: Mit der Referenzübergabe können Sie eine Auslagerungsroutine schreiben, die wie folgt aussieht: temp := a; a := b; b := temp;Und wenn sie zurückkehrt, werden die Werte von aund bausgetauscht. In C gibt es keine Möglichkeit, dies zu tun. Sie müssen Zeiger an aund übergeben, bund die swapRoutine muss auf die Werte reagieren, auf die sie zeigen.
Mason Wheeler

Antworten:

58

Wertübergabe ist häufig sicherer als Referenzübergabe, da Sie die Parameter nicht versehentlich an Ihrer Methode / Funktion ändern können. Dies vereinfacht die Verwendung der Sprache, da Sie sich nicht um die Variablen kümmern müssen, die Sie einer Funktion zuweisen. Sie wissen, dass sie nicht geändert werden, und dies ist oft das, was Sie erwarten .

Wenn Sie jedoch die Parameter ändern möchten , müssen Sie eine explizite Operation ausführen, um dies zu verdeutlichen (übergeben Sie einen Zeiger). Dies zwingt alle Ihre Anrufer, den Anruf etwas anders zu tätigen ( &variablein C), und dies macht deutlich, dass der variable Parameter geändert werden kann.

Nun können Sie davon ausgehen, dass eine Funktion Ihren Variablenparameter nicht ändert, es sei denn, dies ist ausdrücklich markiert (indem Sie einen Zeiger übergeben müssen). Dies ist eine sicherere und sauberere Lösung als die Alternative: Nehmen Sie an, dass alles Ihre Parameter ändern kann, es sei denn, sie geben ausdrücklich an, dass dies nicht möglich ist.

Oleksi
quelle
4
+1 Arrays, die in Zeiger zerfallen, stellen eine bemerkenswerte Ausnahme dar, aber die allgemeine Erklärung ist gut.
dasblinkenlight
6
@dasblinkenlight: Arrays sind ein wunder Punkt in C :(
Matthieu M.
55

Call-by-Value und Call-by-Reference sind Implementierungstechniken, die vor langer Zeit für Parameterübergabemodi gehalten wurden.

Am Anfang war FORTRAN. FORTRAN hatte nur Call-by-Reference, da Subroutinen in der Lage sein mussten, ihre Parameter zu ändern, und Rechenzyklen zu teuer waren, um mehrere Parameterübergabemodi zuzulassen. Außerdem war nicht genug über die Programmierung bekannt, als FORTRAN zum ersten Mal definiert wurde.

ALGOL hat Call-by-Name und Call-by-Value erfunden. Call-by-Value war für Dinge, die nicht geändert werden sollten (Eingabeparameter). Call-by-Name war für Ausgabeparameter. Call-by-Name stellte sich als großes Problem heraus, und ALGOL 68 ließ es fallen.

PASCAL lieferte Call-by-Value und Call-by-Reference. Es gab dem Programmierer keine Möglichkeit, dem Compiler per Referenz mitzuteilen, dass er ein großes Objekt (normalerweise ein Array) übergeben hat, um zu vermeiden, dass der Parameterstapel aufgeblasen wird, aber das Objekt sollte nicht geändert werden.

PASCAL fügte dem Sprachdesign-Lexikon Zeiger hinzu.

C lieferte Call-by-Value und simulierte Call-by-Reference, indem ein Kludge-Operator definiert wurde, der einen Zeiger auf ein beliebiges Objekt im Speicher zurückgibt.

Spätere Sprachen kopierten C, hauptsächlich, weil die Designer noch nie etwas anderes gesehen hatten. Dies ist wahrscheinlich der Grund, warum Call-by-Value so beliebt ist.

C ++ fügte einen Kludge über dem C-Kludge hinzu, um Call-by-Reference bereitzustellen.

Jetzt haben C und C ++ (Programmierer) als direkte Folge von call-by-value vs. call-by-reference vs. call-by-pointer-kludge schreckliche Kopfschmerzen mit const-Zeigern und Zeigern auf const (schreibgeschützt) Objekte.

Ada hat es geschafft, diesen ganzen Albtraum zu vermeiden.

Ada hat kein explizites Call-by-Value im Vergleich zu Call-by-Reference. Vielmehr hat Ada In-Parameter (die gelesen, aber nicht geschrieben werden können), Out-Parameter (die geschrieben werden MÜSSEN, bevor sie gelesen werden können) und In-Out-Parameter, die in beliebiger Reihenfolge gelesen und geschrieben werden können. Der Compiler entscheidet, ob ein bestimmter Parameter als Wert oder als Referenz übergeben wird: Er ist für den Programmierer transparent.

John R. Strohm
quelle
1
+1. Dies ist die einzige Antwort, die ich bis jetzt hier gesehen habe, die die Frage tatsächlich beantwortet und die Sinn macht und die nicht als transparente Entschuldigung für einen Pro-FP-Rant dient.
Mason Wheeler
11
+1 für "Spätere Sprachen kopierten C, hauptsächlich, weil die Designer noch nie etwas anderes gesehen hatten." Jedes Mal, wenn ich eine neue Sprache mit 0-vorangestellten Oktalkonstanten sehe, sterbe ich innerlich ein wenig.
Librik
4
Dies könnte der erste Satz der Bibel der Programmierung sein: In the beginning, there was FORTRAN.
1
Wenn in Bezug auf ADA eine globale Variable an einen In / Out-Parameter übergeben wird, verhindert die Sprache, dass ein verschachtelter Aufruf sie überprüft oder ändert? Wenn nicht, würde ich annehmen, dass es einen deutlichen Unterschied zwischen den Parametern in / out und ref gibt. Ich persönlich möchte, dass Sprachen alle Kombinationen von "in / out / in + out" und "by value / by ref / don't care" explizit unterstützen, damit Programmierer maximale Klarheit über ihre Absichten, aber Optimierer bieten können maximale Flexibilität bei der Implementierung haben [solange es mit der Absicht des Programmierers übereinstimmt].
Supercat
2
@Neikos Es gibt auch die Tatsache, dass das 0Präfix nicht mit jedem anderen Präfix übereinstimmt , das nicht zur Basis 10 gehört. Das mag in C (und meist quellkompatiblen Sprachen, z. B. C ++, Objective C) etwas zu entschuldigen sein, wenn Oktal die einzige alternative Basis bei der Einführung war, aber wenn modernere Sprachen beide 0xund 0von Anfang an verwenden, lassen sie sie nur schlecht aussehen durchdacht.
8bittree
13

Die Verwendung von Referenzdaten ermöglicht sehr subtile, unbeabsichtigte Nebenwirkungen, die kaum zu ermitteln sind, wenn sie das unbeabsichtigte Verhalten hervorrufen.

Vorbei an Wert, vor allem final, staticoder constEingabeparametern machen diese ganze Klasse von Bugs verschwinden.

Unveränderliche Sprachen sind noch deterministischer und leichter zu überlegen und zu verstehen, was in einer Funktion vor sich geht und was voraussichtlich daraus resultiert.


quelle
7
Dies ist eines der Dinge, die Sie herausfinden, nachdem Sie versucht haben, "alten" VB-Code zu debuggen, in dem standardmäßig alles referenziert ist.
Jfrankcarr
Vielleicht ist es nur so, dass mein Hintergrund anders ist, aber ich habe keine Ahnung, über welche Art von "sehr subtilen, unbeabsichtigten Nebenwirkungen, die sehr schwer bis unmöglich zu verfolgen sind" Sie sprechen. Ich bin an Pascal gewöhnt, wobei By-Value der Standardwert ist, aber By-Reference verwendet werden kann, indem der Parameter explizit markiert wird (im Grunde das gleiche Modell wie C #), und ich hatte noch nie Probleme damit. Ich kann sehen, wie problematisch es in "alten VB" sein würde, wo By-Ref die Standardeinstellung ist, aber wenn By-Ref aktiviert ist, werden Sie beim Schreiben darüber nachdenken.
Mason Wheeler
4
@MasonWheeler Was ist mit Neulingen, die hinter dir her kommen und einfach kopieren, was du getan hast, ohne es zu verstehen, und diesen Zustand überall indirekt zu manipulieren? Die reale Welt passiert die ganze Zeit. Weniger Indirektion ist eine gute Sache. Unveränderlich ist am besten.
1
@MasonWheeler Die einfachen Alias-Beispiele, wie die SwapHacks, die keine temporäre Variable verwenden, obwohl sie selbst wahrscheinlich kein Problem darstellen, zeigen, wie schwierig das Debuggen eines komplexeren Beispiels sein kann.
Mark Hurd
1
@MasonWheeler: Das klassische Beispiel in FORTRAN, das zum Sprichwort "Variablen werden nicht; Konstanten werden nicht" führte, würde eine Gleitkommakonstante wie 3.0 an eine Funktion übergeben, die den übergebenen Parameter ändert. Für jede Gleitkommakonstante, die an eine Funktion übergeben wurde, erstellte das System eine "versteckte" Variable, die mit dem richtigen Wert initialisiert und an Funktionen übergeben werden konnte. Wenn die Funktion ihrem Parameter 1.0 hinzufügt, kann eine beliebige Untermenge von 3.0 im Programm plötzlich zu 4.0 werden.
Supercat
8

Warum werden so viele Sprachen als Wert übergeben?

Wenn Sie große Programme in kleine Unterprogramme aufteilen, können Sie die Unterprogramme unabhängig voneinander beurteilen. Durch die Referenzübergabe wird diese Eigenschaft aufgehoben. (Wie geteilter veränderlicher Zustand.)

Sogar Sprachen, in denen Sie explizite Zeiger-Manipulationen wie C durchführen, werden immer als Wert übergeben (Sie können sie als Referenz übergeben, aber das ist nicht das Standardverhalten).

Tatsächlich ist C immer ein Übergabewert, niemals ein Übergabewert. Sie können eine Adresse von etwas nehmen und diese Adresse übergeben, aber die Adresse wird weiterhin als Wert übergeben.

Was ist der Vorteil davon, warum werden so viele Sprachen von Werten und warum werden andere als Referenz weitergegeben ?

Es gibt zwei Hauptgründe für die Verwendung der Referenzübergabe:

  1. Mehrere Rückgabewerte simulieren
  2. Effizienz

Persönlich denke ich, dass # 1 falsch ist: Es ist fast immer eine Entschuldigung für schlechtes API- und / oder Sprachdesign:

  1. Wenn Sie mehrere Rückgabewerte benötigen, simulieren Sie diese nicht, sondern verwenden Sie eine Sprache, die diese unterstützt.
  2. Sie können auch mehrere Rückgabewerte simulieren, indem Sie sie in eine einfache Datenstruktur wie ein Tupel packen. Dies funktioniert besonders gut, wenn die Sprache Pattern Matching oder Destructuring Bind unterstützt. ZB Ruby:

    def foo
      # This is actually just a single return value, an array: [1, 2, 3]
      return 1, 2, 3
    end
    
    # Ruby supports destructuring bind for arrays: a, b, c = [1, 2, 3]
    one, two, three = foo
    
  3. Oftmals benötigen Sie nicht einmal mehrere Rückgabewerte. Ein bekanntes Muster ist beispielsweise, dass die Subroutine einen Fehlercode zurückgibt und das tatsächliche Ergebnis als Referenz zurückgeschrieben wird. Stattdessen sollten Sie nur eine Ausnahme auslösen, wenn der Fehler unerwartet ist, oder eine zurückgeben, Either<Exception, T>wenn der Fehler erwartet wird. Ein weiteres Muster besteht darin, einen Booleschen Wert zurückzugeben, der angibt, ob die Operation erfolgreich war, und das tatsächliche Ergebnis als Referenz zurückzugeben. Wenn der Fehler unerwartet ist, sollten Sie stattdessen eine Ausnahme auslösen. Wenn der Fehler erwartet wird, z. B. wenn Sie einen Wert in einem Wörterbuch suchen, sollten Sie Maybe<T>stattdessen eine zurückgeben.

Referenzübergabe ist möglicherweise effizienter als Wertübergabe, da Sie den Wert nicht kopieren müssen.

(Ich verstehe, dass Haskell als Referenz übergeben wird, obwohl ich nicht sicher bin).

Nein, Haskell wird nicht als Referenz übergeben. Es ist auch kein Pass-by-Value. Referenzübergabe und Wertübergabe sind beide strenge Bewertungsstrategien, aber Haskell ist nicht strikt.

Tatsächlich legt die Haskell-Spezifikation keine bestimmte Bewertungsstrategie fest. Die meisten Hakell-Implementierungen verwenden eine Mischung aus Call-by-Name und Call-by-Need (eine Variante von Call-by-Name mit Memoization), der Standard schreibt dies jedoch nicht vor.

Beachten Sie, dass es auch für strenge Sprachen keinen Sinn macht, zwischen Referenzübergabe und Wertübergabe für funktionale Sprachen zu unterscheiden, da der Unterschied zwischen ihnen nur beobachtet werden kann, wenn Sie die Referenz ändern. Daher kann der Implementierer frei zwischen den beiden wählen, ohne die Semantik der Sprache zu brechen.

Jörg W. Mittag
quelle
9
"Wenn Sie mehrere Rückgabewerte benötigen, simulieren Sie diese nicht, sondern verwenden Sie eine Sprache, die sie unterstützt." Das ist etwas seltsam zu sagen. Es ist im Grunde sagen : „Wenn Sie Ihre Sprache alles tun kann , was Sie brauchen, aber ein Merkmal , dass Sie wahrscheinlich in weniger als 1% des Codes benötigen - aber immer noch wird es für das 1% brauchen - nicht in getan werden Eine besonders saubere Art und Weise, dann ist Ihre Sprache nicht gut genug für Ihr Projekt und Sie sollten das Ganze in einer anderen Sprache schreiben. " Entschuldigung, aber das ist einfach lächerlich.
Mason Wheeler
+1 Ich stimme diesem Punkt voll und ganz zu. Das Aufteilen großer Programme in kleine Unterprogramme besteht darin, dass Sie die Unterprogramme unabhängig voneinander beurteilen können. Durch die Referenzübergabe wird diese Eigenschaft aufgehoben. (Wie geteilter veränderlicher Zustand.)
Rémi
3

Abhängig vom aufrufenden Modell der Sprache, der Art der Argumente und den Speichermodellen der Sprache gibt es ein unterschiedliches Verhalten.

Bei einfachen systemeigenen Typen können Sie den Wert durch Übergeben von Werten an die Register übergeben. Das kann sehr schnell gehen, da der Wert weder aus dem Speicher geladen noch zurückgespeichert werden muss. Eine ähnliche Optimierung ist auch möglich, indem der von dem Argument verwendete Speicher einfach wiederverwendet wird, sobald der Angerufene damit fertig ist, ohne das Risiko einzugehen, die aufrufende Kopie des Objekts zu beschädigen. Wenn das Argument ein temporäres Objekt wäre, müssten Sie wahrscheinlich eine vollständige Kopie speichern (C ++ 11 macht diese Art der Optimierung mit der neuen Rechtsreferenz und ihrer Verschiebungssemantik noch deutlicher).

In vielen OO-Sprachen (C ++ ist in diesem Kontext eher eine Ausnahme) können Sie ein Objekt nicht als Wert übergeben. Sie sind gezwungen, es als Referenz weiterzugeben. Dadurch wird der Code standardmäßig polymorph und entspricht eher dem Begriff der für OO geeigneten Instanzen. Wenn Sie einen Wert angeben möchten, müssen Sie selbst eine Kopie erstellen und dabei die Leistungskosten berücksichtigen, die zu einer solchen Aktion geführt haben. In diesem Fall wählt die Sprache für Sie den Ansatz, mit dem Sie mit größerer Wahrscheinlichkeit die beste Leistung erzielen.

Bei funktionalen Sprachen ist die Weitergabe von Werten oder Referenzen wohl nur eine Frage der Optimierung. Da die Funktionen in einer solchen Sprache rein und so nebenwirkungsfrei sind, gibt es wirklich keinen Grund, den Wert außer der Geschwindigkeit zu kopieren. Ich bin mir sogar ziemlich sicher, dass eine solche Sprache häufig dieselbe Kopie von Objekten mit denselben Werten verwendet, eine Möglichkeit, die nur verfügbar ist, wenn Sie eine (const) -Referenzsemantik verwendet haben. Python verwendete diesen Trick auch für Ganzzahlen und allgemeine Zeichenfolgen (wie Methoden und Klassennamen), um zu erklären, warum Ganzzahlen und Zeichenfolgen in Python konstante Objekte sind. Dies trägt auch dazu bei, den Code erneut zu optimieren, indem beispielsweise ein Zeigervergleich anstelle eines Inhaltsvergleichs möglich ist und einige interne Daten verzögert ausgewertet werden.

Fabien Ninoles
quelle
2

Sie können einen Ausdruck als Wert übergeben, das ist natürlich. Ausdruck als (vorübergehende) Referenz zu übergeben ist ... seltsam.

herby
quelle
1
Das Übergeben eines Ausdrucks als temporärer Verweis kann auch zu schlechten Fehlern führen (in einer statusbehafteten Sprache), wenn Sie ihn glücklich ändern (da er nur temporär ist). Wenn Sie jedoch eine Variable übergeben, wird der Fehler zurückgesetzt, und Sie müssen eine hässliche Problemumgehung wie das Übergeben von foo entwickeln +0 statt foo
Herby
2

Wenn Sie als Referenz übergeben, dann arbeiten Sie effektiv immer mit globalen Werten, mit allen Problemen der globalen Werte (nämlich Umfang und unbeabsichtigte Nebenwirkungen).

Referenzen sind wie globale Referenzen manchmal von Vorteil, sollten aber nicht Ihre erste Wahl sein.

jmoreno
quelle
3
Ich nehme an, Sie meinten, im ersten Satz als Referenz weiterzugeben?
jk.