[vertagt] Copy-Constructor bei Call-by-Value
-
Hallo,
ich wüsste gerne wer, caller oder callee, bei einem Funktionsaufruf mit einer Klasse als Parameter (Call-by-Value) den Copy-Constructor aufruft. Ich nehme mal an das die Klasse dann auf dem Stack, in der Art wie bei cdecl, abgelegt wird, also vom caller. Ich glaube nicht das der callee die Klasse noch mal kopiert, in seinen Stack-Frame.
Wie sieht das eigentlich bei variablen Parameterlisten aus? Ich gehe davon aus das der caller die Parameter, auch komplexe Klassen (und falls vorhanden mit dem Copy-Constructor), auf den Stack ablegt. Wenn dann die entsprechenden Parameter per va_arg ausgelesen werden wird doch eigentlich noch mal kopiert und dann müsste doch noch mal der Copy-Constructor aufgerufen werden, oder irre ich? Wie bestimmt eigentlich va_arg wie groß das Parameter ist? Bei Klassen könnte ja eine geerbte größere Klasse drin sein.
Steht über diese Details irgendetwas in den Standards?
Grüße
Erik
-
So ein technischer Kram steht nicht im Standard, das ist alleine eine Sache für den Compilerhersteller. Warum sollte den Programmierer sowas interessieren? Der Compilerhersteller wird schon wissen, was er da tut.
-
Hallo,
SeppJ schrieb:
So ein technischer Kram steht nicht im Standard, das ist alleine eine Sache für den Compilerhersteller.
Du willst mir damit sagen das ich mir mal ansehen sollte wie die Compilermacher, vorzugsweise bei Open-Source-Compilern, das angehen.
SeppJ schrieb:
Warum sollte den Programmierer sowas interessieren?
Damit er genau weiß was wann in seinem Programm wie passiert.
Wenn er das nicht wissen will kann er auch Java o.ä. benutzen.
(keine Angst das soll kein Flame-War werden)SeppJ schrieb:
Der Compilerhersteller wird schon wissen, was er da tut.
Ja, der schaut was derjenige der das ABI definiert so schönes geschrieben hat. Und damit ist das Problem auf meinem Tisch angekommen.
Und ich frag nun hier ob jemand nette Ideen für mich hat.Grüße
Erik
-
erik.vikinger schrieb:
Hallo,
SeppJ schrieb:
So ein technischer Kram steht nicht im Standard, das ist alleine eine Sache für den Compilerhersteller.
Du willst mir damit sagen das ich mir mal ansehen sollte wie die Compilermacher, vorzugsweise bei Open-Source-Compilern, das angehen.
Diese Informationen kannst du nur für einen spezifschen Compiler und alleine schon nur ein Build herausfinden. In der nächsten Version kann das alles schon wieder anderst gehandhabt werden.
SeppJ schrieb:
Warum sollte den Programmierer sowas interessieren?
Damit er genau weiß was wann in seinem Programm wie passiert.
Wenn er das nicht wissen will kann er auch Java o.ä. benutzen.
(keine Angst das soll kein Flame-War werden)So etwas musst du als Programmier nicht wissen (das wäre gerade zu tödlich), um zu wissen, was passiert. (Klar ist es interessant, aber man muss irgendwo eine Grenze ziehen, um nicht all zu tief runter zu müssen).
SeppJ schrieb:
Der Compilerhersteller wird schon wissen, was er da tut.
Ja, der schaut was derjenige der das ABI definiert so schönes geschrieben hat. Und damit ist das Problem auf meinem Tisch angekommen.
Und ich frag nun hier ob jemand nette Ideen für mich hat.Wie gesagt der Compilerhersteller ist da weitestgehend frei, wie er das macht. Wenn du exakt wissen willst, wie er das macht, dann kommst du um den Code von denen nicht herum. (oder halt ein Artikel, von jemanden, der das schon gemacht hat).
Wenn du dich für so Sachen interessierst, dann solltest du dich besser mit Compilerbau beschäftigen, als mit C++.

-
erik.vikinger schrieb:
SeppJ schrieb:
Warum sollte den Programmierer sowas interessieren?
Damit er genau weiß was wann in seinem Programm wie passiert.
Wenn er das nicht wissen will kann er auch Java o.ä. benutzen.
(keine Angst das soll kein Flame-War werden)Interresiert dich das wirklich was da am schluß rauskommt oder willst du einen hochperformanten code? Du kannst noch nicht einmal sagen wie er welche Codestelle genau Kompaliert welche optimierungs Algorithmen dadrüberlaufen. Wenn du ganau wissen willst wie was passiert dann muss du schon Assambler nehmen das kannst du bei C und bei C++ nämlich nicht merh genau sagen.
-
Immer dran denken, dass C++ ein ganz abstraktes Gebilde ist. Es ist eine Programmiersprache und gibt keinerlei Vorgaben, wie sie technisch umgesetzt wird. Man könnte mit Wasser, Kokosnusshälften, dressierten Affen und ausgehöhlten Baumstämmen einen Dschungelcomputer bauen. Total primitiv, und trotzdem könnte man jedes dem standardgemäße C++ Programm darauf ausführen. Man muss nur einen Compiler für diesen Computer haben. Und der Dschungelcompiler muss für seine Programme nicht zwangsläufig eine Stackstruktur benutzen. Möglicherweise kann der Dschungelcomputer solche Datenstrukturen gar nicht abbilden.
-
Hallo,
drakon schrieb:
Diese Informationen kannst du nur für einen spezifschen Compiler und alleine schon nur ein Build herausfinden. In der nächsten Version kann das alles schon wieder anderst gehandhabt werden.
Das würde bedeuten das man Object-Files von verschiedenen Compilerversionen nicht zusammenlinken könnte. Ich denke schon das sowas in irgendwelchen CPU-Architektur spezifischen ABI-Spezifikationen drin stehen müsste. Ich bin gerade dabei eben so eine ABI-Spezifikationen für eine neue CPU-Architektur zu erarbeiten. Da ich davon ausgehe das alle anderen auch nur mit Wasser kochen (Register und Stack sind bei allen mir bekannten CPUs grundsätzlich doch sehr ähnlich aufgebaut) hatte ich die Hoffnung mir von denen ein paar Ideen abschauen zu können.
drakon schrieb:
So etwas musst du als Programmier nicht wissen ....
Ich denke für Klassen wie smart-pointer u.ä. sollte man schon ungefähr wissen wie sowas abläuft. Bei einer Klasse die als vararg auf den Stack kopiert wurde muss ja nach dem Funktionsaufruf (bevor der nachfolgende Code des callers dran kommt) auch wieder der Destructor aufgerufen werden.
drakon schrieb:
Wie gesagt der Compilerhersteller ist da weitestgehend frei ....
Sicher? Das würde IMHO ziemliche Probleme verursachen, wenn das jeder so machen dürfte wie er will.
GAMES1990 schrieb:
Interresiert dich das wirklich was da am schluß rauskommt oder willst du einen hochperformanten code?
Bei internen statischen Funktionen kann der Compiler sich frei austoben aber bei Funktionen die aus anderen Modulen aufgerufen werden sollen muss der Compiler sich an die geltende ABI-Spezifikation und entsprechende Calling-Conventions halten.
SeppJ schrieb:
Man könnte mit Wasser, Kokosnusshälften, dressierten Affen und ausgehöhlten Baumstämmen einen Dschungelcomputer bauen.
Ein wirklich interessantes Projekt (auch wenn ich das lieber in einem FPGA mache), könntest Du bitte ein paar Bilder und eventuell ein paar Benchmarkergebnisse schicken wenn es fertig ist.

Ich verstehe was Du sagen möchtest. Ich dachte nur das da C++ verschiedene Zusagen macht, sowas wie das jedes Objekt wieder ordentlich abgeräumt und dessen Destructor aufgerufen wird wenn sein Gültigkeitsbereich vorbei ist.Ich schätze das Beste wird sein ich baue mal ein paar Beispiele und sehe mir an was der Compiler generiert hat.
Grüße
Erik
-
erik.vikinger schrieb:
Das würde bedeuten das man Object-Files von verschiedenen Compilerversionen nicht zusammenlinken könnte.
Korrekt und ist in C++ auch der Fall.
erik.vikinger schrieb:
Ich denke schon das sowas in irgendwelchen CPU-Architektur spezifischen ABI-Spezifikationen drin stehen müsste.
Ja, das steht irgendwo, wird aber von den Kompilerherstellern erstellt und ist bei C++ kein Standard. C dagegen hat einen ABI Standard.
erik.vikinger schrieb:
Ich denke für Klassen wie smart-pointer u.ä. sollte man schon ungefähr wissen wie sowas abläuft.
Wie? Wieso muss ich das bei einem Smart-Pointer wissen? Leuchtet mir nicht so wirklich ein.
erik.vikinger schrieb:
Sicher? Das würde IMHO ziemliche Probleme verursachen, wenn das jeder so machen dürfte wie er will.
Das ist sehr sicher. Bei C++ gibt es keinen ABI Standard. Bei C gibt es einen ABI Standard, wie schon weiter oben geschrieben. Und ja, das gibt Probleme, massig sogar. Nicht ohne Grund wünsche ich mir schon lange vom C++ Standard Komittee einen C++ ABI Standard

erik.vikinger schrieb:
Bei internen statischen Funktionen kann der Compiler sich frei austoben aber bei Funktionen die aus anderen Modulen aufgerufen werden sollen muss der Compiler sich an die geltende ABI-Spezifikation und entsprechende Calling-Conventions halten.
Welche es eben bei C++ nicht gibt, weshalb man meistens Libraries nicht zwischen Kompilern austauschen kann. Also Programm und Bibliothek muss vom gleichen Kompiler erstellt worden sein, wenn man C++ Dinge über die Bibliotheksgrenze hinweg verwenden möchte. Nicht ohne Grund, greift man oft bei solchen Dingen auf C zurück. Auch in C++ mit:
extern "C" void foo();Diese Funktion wird nun als C Funktion exportiert und ist somit einem ABI Standard unterworfen.
erik.vikinger schrieb:
Ich dachte nur das da C++ verschiedene Zusagen macht, sowas wie das jedes Objekt wieder ordentlich abgeräumt und dessen Destructor aufgerufen wird wenn sein Gültigkeitsbereich vorbei ist.
Schön und gut, aber es gibt keine weiteren Aussagen darüber, wie das ein Kompiler genau umsetzen muss. Dass wird einfach gefordert, die Umsetzung wird den Kompilerherstellern frei überlassen.
Grüssli
-
Hallo,
Dravere schrieb:
Wie? Wieso muss ich das bei einem Smart-Pointer wissen? Leuchtet mir nicht so wirklich ein.
Ich dachte der smart-pointer muss genau wissen das und wie er kopiert wird damit er das referenzierte Objekt freigeben kann wenn es keinen smart-pointer mehr gibt der darauf verweist. Ich kenne mich aber nicht wirklich mit smart-pointer aus, kann also auch falsch sein was ich darüber denke.
Dravere schrieb:
Bei C++ gibt es keinen ABI Standard.
Echt? Nichtmal grobe Guide-Lines? Ich hab z.B. mal in einer ABI-Spezifikation (glaube für ARM) gelesen wie der this-pointer bei Methodenaufrufen übergeben werden soll, hatte mich damals aber nicht weiter damit beschäftigt, und bin daher davon ausgegangen das C++ ähnlich vollständig geregelt ist wie C. Gibt es denn wenigstens ein paar C++-Regeln für Call-by-Value mit Klassen?Dravere schrieb:
Bei C gibt es einen ABI Standard,
Den hab ich fast fertig und dabei sind mir nun ein paar Dinge für C++ eingefallen.
Dravere schrieb:
Nicht ohne Grund wünsche ich mir schon lange vom C++ Standard Komittee einen C++ ABI Standard

Ich jetzt auch. Ist denn sowas schon in Arbeit?
Danke
Erik
-
erik.vikinger schrieb:
Dravere schrieb:
Bei C++ gibt es keinen ABI Standard.
Echt? Nichtmal grobe Guide-Lines? Ich hab z.B. mal in einer ABI-Spezifikation (glaube für ARM) gelesen wie der this-pointer bei Methodenaufrufen übergeben werden soll, hatte mich damals aber nicht weiter damit beschäftigt, und bin daher davon ausgegangen das C++ ähnlich vollständig geregelt ist wie C. Gibt es denn wenigstens ein paar C++-Regeln für Call-by-Value mit Klassen?Alles, was man haben und erfüllen muss, um einen C++ Compiler zu bauen, ist der C++ Standard und da wird keinerlei Implementierungsdetail besprochen, geschweige den etwas, dass sich auf asm Ebene befindet. Du musst dich aber genau an den Standard halten und das impliziert bei gewissen Sachen eine übliche Implementierung. Aber der Punkt ist, dass man da völlig frei ist und den Standard einhält, kann man jegliche Optimierung machen.
erik.vikinger schrieb:
Dravere schrieb:
Nicht ohne Grund wünsche ich mir schon lange vom C++ Standard Komittee einen C++ ABI Standard

Ich jetzt auch. Ist denn sowas schon in Arbeit?
Ich bezweifle das stark und ich bezweifle auch, dass das Stanrdisierungskomittee die richtigen Leute dafür wären. Im Moment haben sie genug zu tun mit dem C++0x.
- Sich jetzt noch mit solchen Detailfragen zu beschäftigen wäre einfach zu viel, wenn man bedenkt, wie lange es geht bis alleine die Sprache genügend für einen Standard beschrieben ist, dann kann man das gut verstehen.
-
Hallo,
drakon schrieb:
Alles, was man haben und erfüllen muss, um einen C++ Compiler zu bauen, ist der C++ Standard und da wird keinerlei Implementierungsdetail besprochen,
Verstanden.
Ist denn zumindest geregelt wer (caller oder callee) bei Call-by-Value den Copy-Constructor aufruft? Wie wird das eigentlich beim Return-Value gemacht? Zumindest ein paar Dinge müssten dazu doch im C++-Standard stehen. Oder muss man sich das aus allgemeinen Handhabungsregeln für Klassen herleiten?Grüße
Erik
-
SeppJ schrieb:
So ein technischer Kram steht nicht im Standard, das ist alleine eine Sache für den Compilerhersteller. Warum sollte den Programmierer sowas interessieren? Der Compilerhersteller wird schon wissen, was er da tut.
Das reicht doch als Antwort oder?
Schau doch einfac selber in den Standard und du wirst sehn, dass der sich nicht mit solchen technischen Einzelheiten aufhält.
-
erik.vikinger schrieb:
Ist denn zumindest geregelt wer (caller oder callee) bei Call-by-Value den Copy-Constructor aufruft?
Wer? Das würde der Computer sein.
Die Frage verstehe ich in mehr als einer Hinsicht nicht. Der Standard spezifiziert, welche Nebeneffekte ein bestimmtes Stück Programmcode hat und ggf. in welcher Reihenfolge diese Effekte auftreten, wenn und soweit es das beobachtbare Verhalten des Programmes beeinflusst.
Eine dieser Regeln (5.2.2/8) besagt:[...] All side effects of argument expression evaluations take effect before the function is entered. [...]
Das ist allerdings nicht unbedingt die geforderte Antwort.
-
erik.vikinger schrieb:
Dravere schrieb:
Wie? Wieso muss ich das bei einem Smart-Pointer wissen? Leuchtet mir nicht so wirklich ein.
Ich dachte der smart-pointer muss genau wissen das und wie er kopiert wird damit er das referenzierte Objekt freigeben kann wenn es keinen smart-pointer mehr gibt der darauf verweist.
Das ist die Implementation eines Shared Pointers (es gibt noch andere Smart Pointer). Allerdings geschieht dies alles auf C++ Ebene. Ein Shared Pointer hat seinen eigenen operator= und Kopierkonstruktor. Darin wird der Zähler inkrementiert und dann im Destruktor dekrementiert. Wenn er nach dem dekrementieren 0 ist, so wird das Objekt gelöscht, auf welches er zeigt.
Eine ganz billige Implementation:
template<typename T> class SharedPtr { private: int* m_counter; T* m_object; public: explicit SharedPtr(T* ptr) : m_counter(new int(1)) , m_object(ptr) { } SharedPtr(SharedPtr const& obj) : m_counter(obj.m_counter) , m_object(obj.m_object) { ++*m_counter; } ~SharedPtr() { --*m_counter; if(!*m_counter) { delete m_object; delete m_counter; } } SharedPtr& operator =(SharedPtr const& obj) { if(this == &obj) return *this; m_counter = obj.m_counter; m_object = obj.m_object; ++*m_counter; } T* operator ->() const { return m_object; } };(wirklich ganz billig, aber soll ja nur zur Veranschaulichung dienen. :))
Grüssli
-
erik.vikinger schrieb:
Dravere schrieb:
Wie? Wieso muss ich das bei einem Smart-Pointer wissen? Leuchtet mir nicht so wirklich ein.
Ich dachte der smart-pointer muss genau wissen das und wie er kopiert wird damit er das referenzierte Objekt freigeben kann wenn es keinen smart-pointer mehr gibt der darauf verweist. Ich kenne mich aber nicht wirklich mit smart-pointer aus, kann also auch falsch sein was ich darüber denke.
Um einen Smart-Pointer zu schreiben, reicht Standard-C++. Mit Kopierkonstruktor, Zuweisungsoperator und Destruktor kann das gewünschte Verhalten auf eine ziemlich naheliegende Weise implementiert werden. Die Reihenfolge der für das Programm relevanten Anweisungen (bezüglich Programmlogik und Seiteneffekten) wird vom Standard in fast allen vernünftigen Fällen vorgeschrieben. Wäre ja schlimm, wenn man für einfachste Logik wie Reference Counting die Compiler-Implementierung kennen müsste.
-
@Dravere
Warum hast du den counter dynamisch angelegt? - Du hast so nämlich ein potentielles Memoryleak.
-
drakon schrieb:
@Dravere
Warum hast du den counter dynamisch angelegt? - Du hast so nämlich ein potentielles Memoryleak.Überleg mal ganz stark. Jedes SharedPtr Objekt muss einen Referenzzähler haben und dieser Referenzzähler muss ... aja, von allen geteilt werden. Wie willst du das anders machen?

Normalerweise verpackt man das ganze ein wenig schöner in eine interne Struktur, welche dann noch zusätzliche Daten für das Freigeben des Speichers mitabspeichert. Aber du kommst nicht drum herum, dies irgendwie dynamisch anzulegen, weil es eben alle SharedPtr, welche auf dieses Objekt verweisen, untereinander teilen müssen.
Grüssli
-
drakon schrieb:
@Dravere
Warum hast du den counter dynamisch angelegt? - Du hast so nämlich ein potentielles Memoryleak.Wahrscheinlich damit alle Instanzen auf den Counter zugreifen können.

Allerdings würde ich den Zuweisungsoperator über Copy&Swap implementieren, auch wenn hier Exceptionsicherheit kein grosses Problem darstellt. Swap sollte jeder Smart-Pointer sowieso anbieten.
-
Nexus schrieb:
Allerdings würde ich den Zuweisungsoperator über Copy&Swap implementieren, auch wenn hier Exceptionsicherheit kein grosses Problem darstellt. Swap sollte jeder Smart-Pointer sowieso anbieten.
Ich sagte ja ganz billig :p
Bei einer eigenen Implementation würde ich das ganz anders machen ... mehr als ganz anders
Grüssli
-
Ok, ja. Da habe ich nicht richtig geschaut. Aber trotzdem, wenn beim Konstruktor bei dem new eine Exception geworfen wird, dann wird der Speicher des übergebenen Zeigers nicht freigegeben.
Sprich ich würde die Allokation in den Konstruktor verschieben und dann dort eine allfällige Exception fangen, den Speicher freigeben und weiterwerfen.