warum ist Call by value nur eine Kopie?



  • Hallo.

    Ich würde gern eine Frage stellen, von der ich hoffe das diese nicht schon in einem ähnlichen Thread beantwortet wurde.
    Entschuldigt, sollte ich diese nicht gefunden haben. Suche heut schon eine geschlagene Stunde nach einer Antwort, so langsam geht meine gedullt mit google zu Ende..

    Es geht allgemein um Werteübergabe an Funktionen.
    Ich weiß, das es "Call by value" & "Call by reference" gibt.
    Was ich aber wissen möchte, die Frage kam mir bei einer Funktion, die 2 Werte tauschen soll (swap), warum übergebe ich der Funktion die Werte nur als Kopie.(ich möchte nicht wissen warum ich das so mache, könnte den Wert per Referenz oder Zeiger übergeben. Sondern warum es eine Kopie ist...was macht die Kopie zur Kopie?.)
    Ich weiß wie man diese als Referenz oder als Zeiger übergibt, warum ist "Call by value" nur eine Kopie?
    Es wird etwas mit dem stack zu tun haben denke ich, finde aber keine Antwort, warum die Kopie "nur" eine Kopie ist?

    Ich schaffe es gerade nicht meine Frage anders zu formulierten, hoffe aber das diese trotzdem verstanden wird.

    Würde mich über Antwort freuen, auch wenn es keine konkrete Antwort geben sollte, und Danke im voraus.
    Grüsse.



  • @tryn4x Es wird nur der Wert (einer Variablen) weiter gegeben.
    Wie soll das - wenn nicht als Kopie - denn sonst geschehen?

    Bei einem Würfelspiel (z.B. Kniffel) schreibst du ja auch nur eine Kopie der Werte auf.



  • @tryn4x sagte in warum ist Call by value nur eine Kopie?:

    warum ist "Call by value" nur eine Kopie?

    Weil das der Unterschied zwischen call by value und call bei reference ist.



  • Der Aufrufer muß IMMER eine Kopie erstellen, um einer Funktion ein Argument zu übergeben. Das liegt daran, daß es eine Aufrufkonvention gibt, damit die Funktion die Parameter auch findet. Üblich ist, daß eine Kopie auf dem Stapel (Stack) erstellt wird, denn dafür ist er unter anderem da.

    Bei "Call by value" muß nun nichts weiteres unternommen werden. Die aufgerufene Funktion erhält eine Kopie des Arguments und kann mit diesem machen, was sie will. Der Aufrufer merkt von Veränderungen üblicherweise nichts.

    Bei "Call by reference" wird nun statt der Kopie, die Speicheradresse des Originals übergeben. Das Original darf sich daher nicht in einem CPU-Register befinden, da dieses keine Adresse hat. Der Aufgerufene kann zwar nun die Adresse modifizieren, aber die ist ja wiederum eine Kopie. Davon würde der Aufrufer nichts mitbekommen. Der Aufgerufen hat aber nun die Adresse des Originals und kann daher das Original ändern.



  • @mgaeckler sagte in warum ist Call by value nur eine Kopie?:

    Das Original darf sich daher nicht in einem CPU-Register befinden, da dieses keine Adresse hat.

    Diesen Satz würde ich nicht so unterschreiben: Erstens gibt es spätestens seit C++17 kein Sprachkonzept mehr, das einem CPU-Register entspricht und zweitens habe ich schon zu viele Compiler-Optimierungen bewundern dürfen, bei denen in der C++-Repräsentation Werte per Referenz durch etliche Funktionen geschleust wurden und letztendlich alles in CPU-Registern gehalten wurde - einschliesslich des "Originals".

    Aber ja, wenn die Funktion nicht ge-inlined wird und tatsächlich auch im Objektcode als "Maschinencode-Funktion" vorliegt, dann nimmt sie bei Referenzen nicht die Werte selbst in Registern entgegen (wie bei Call by Value-Parametern), sondern eine Adresse auf das Original. Dann müssen diese auch tatsächlich im Speicher liegen. Das ist dann aber nicht mehr wirklich C++-spezifisch.



  • @tryn4x sagte in warum ist Call by value nur eine Kopie?:

    Was ich aber wissen möchte, die Frage kam mir bei einer Funktion, die 2 Werte tauschen soll (swap), warum übergebe ich der Funktion die Werte nur als Kopie.(ich möchte nicht wissen warum ich das so mache, könnte den Wert per Referenz oder Zeiger übergeben. Sondern warum es eine Kopie ist...was macht die Kopie zur Kopie?.)

    Ein swap() das Kopien nimmt x) kann nicht wirklich funktionieren. Wie stellst Du Dir das vor?

    x) (wenn die kopierten Werte nicht die Adressen der betreffenden Objekte sind)



  • @Finnegan Ich rede hier jetzt nichts von irgendetwas C++ spezifischen. Das von mir geschriebende gilt für jede beliebige Programmiersprache. Bei Call by Referenz wird nun mal, statt dem Wert eine Referenz (aka Adresse) übergeben und CPU Register haben für gewöhnlich keine Adresse (zumindest kenne ich keine CPU bei der das nicht gilt).



  • @mgaeckler sagte in warum ist Call by value nur eine Kopie?:

    @Finnegan Ich rede hier jetzt nichts von irgendetwas C++ spezifischen. Das von mir geschriebende gilt für jede beliebige Programmiersprache. Bei Call by Referenz wird nun mal, statt dem Wert eine Referenz (aka Adresse) übergeben und CPU Register haben für gewöhnlich keine Adresse (zumindest kenne ich keine CPU bei der das nicht gilt).

    Ja, wie ich geschrieben habe, ist das unoptimiertem Code ohne Inlining oft so. Mindestens genau so oft jedoch nicht. Dann liegt alles in Registern und es findet auch kein wirklicher Funktionsaufruf (CALL) statt, obwohl die Variablen im C++-Code per Referenz weitergereicht werden.

    Daher finde ich die Aussage "Das Original darf sich daher nicht in einem CPU-Register befinden" nicht ganz korrekt und sogar irreführend, weil es eben nichts mit C++ zu tun hat (seit C++17 gibt es das register-Schlüsselwort nicht mehr, das vorher ohnehin schon nicht bindend war).

    Beispiel: https://godbolt.org/z/zfjnco

    void mul(int& result, const int& a, const int& b)
    {
        result = a * b;
    }
    
    auto test(int a, int b) -> int
    {
        int result;
        mul(result, a, b);
        return result;
    }
    

    wird zu:

    test(int, int):
            mov     eax, edi
            imul    eax, esi
            ret
    

    Die Parameter-"Originale" a und b werden in Registern edi und esi übergeben und das lokale "Original" result wird in eax gehalten. Da sind nur Register involviert, kein Speicher, keine Adresse und auch keine CALL-Instruktion zur mul-Funktion.

    Worauf du hinauswillst ist das, wenn kein Inlining stattfindet und die Funktion also als eigene Entität im Objektcode landet:

    mul(int&, int const&, int const&):
            mov     eax, dword ptr [rdx]
            imul    eax, dword ptr [rsi]
            mov     dword ptr [rdi], eax
            ret
    

    Hier werden in der Tat Speicheradressen in den Registern rdx, rsi und rdi an die Funktion übergeben.

    Das ist aber nicht zwingend so, sondern eine Entscheidung des Compilers oder eine Notwendigkeit der Aufrufkonvention und des Kontextes, in dem die Funktion aufgerufen werden soll. Darauf will ich hinaus.



  • @mgaeckler sagte in warum ist Call by value nur eine Kopie?:

    Das von mir geschriebende gilt für jede beliebige Programmiersprache.

    Ja ne, es gilt eben nicht für irgendeine Sprache sondern höchstens auf Maschinenebene wenn der Code der tatsächlich ausgeführt ist bekannt ist.
    Aber back on topic?



  • Es gilt für alle Sprachen sobald ein Aufruf passiert der der offiziellen Calling-Convention des Systems entsprechen muss.


  • Mod

    @hustbaer sagte in warum ist Call by value nur eine Kopie?:

    Es gilt für alle Sprachen sobald ein Aufruf passiert der der offiziellen Calling-Convention des Systems entsprechen muss.

    Was bei all den virtuellen Laufzeitumgebungen vieler beliebter und verbreiteter Sprachen eine nicht ganz erhebliche Einschränkung ist. Wobei in solchen Fällen das Konzept des CPU-Registers erst recht verloren gegangen ist…



  • @SeppJ sagte in warum ist Call by value nur eine Kopie?:

    Was bei all den virtuellen Laufzeitumgebungen vieler beliebter und verbreiteter Sprachen eine nicht ganz erhebliche Einschränkung ist. Wobei in solchen Fällen das Konzept des CPU-Registers erst recht verloren gegangen ist…

    Richtig. Ich wollte die ursprüngliche Aussage auch gar nicht als solche (=allgemein formuliert) verteidigen. Sondern einen Fall nennen, der einfach zu beschreiben ist, in dem sie gilt. Und dabei darauf hinweisen dass die Sprache - in diesem Fall - wirklich egal ist.

    Davon abgesehen kenne ich jetzt keine "Hochsprache" die ein CPU Register Konzept hat. Soweit ich weiss hatten das nichtmal C und C++ vor Entfernung des register Keywords. Ist ja lediglich ein Tip an den Compiler dass diese Variable oft verwendet wird und daher eben "günstig" platziert werden sollte. Betrifft also nicht nur VM-basierte Sprachen/Systeme, sondern quasi jede Hochsprache.


Log in to reply