free vs. delete bei in Argumentfunktion ausgelageter Speicherallokierung



  • Hallo,

    ich entwickle ein Framework, das eine vom Benutzer zu implementierende abstrakte Methode

    virtual void* foo(int* pSize) = 0;
    

    bereitstellt. Der Benutzer muss innerhalb von foo Speicher allokieren, den ich aus meinem Frameworks heraus wieder freigeben muss. Da ich nicht weiß, ob der Benutzer malloc oder new nutzt, stellt sich mir nun die Frage, ob ich free oder delete aufzurufen habe? Oder spielt das keine Rolle!?



  • malloc/alloc etcc ist C, new ist c++, analog dazu ist free C und delete C++

    Speicher der mit new allociert wurde sollte auch mit delete freigegeben werden und umgekehrt.

    es geht zwar auch mit free allocierte speicher mit delete zu löschen, aber man sollte es nicht machen. könnte unerwartete effekte haben. zumindest was ich bisher so gelesen hab.



  • Hallo,

    Entweder du schreibst in deine Dokumentation, dass der Benutzer new zu verwenden hat oder du stellst auch noch eine abstrakte Deallokationsmethode bereit, die auch noch vom Benutzer überschrieben werden muß.



  • @gurti: Was für "unerwartete Effekte" können das denn sein?

    Die Alternative wäre, dem Nutzer eine Methode

    virtual void foo(void* pBuffer, int* pSize) = 0;
    

    anzubieten und als Parameter die Startadresse und die Größe eines bereits allokierten Puffers zu übergeben. Das möchte ich aber vermeiden, da (1) die Größe des Puffers möglicherweise nicht ausreicht und (2) möglicherweise viel zu viel Speicher bereitgestellt wird.

    Dem Benutzer das new() per Dokumentation zu verbieten geht natürlich immer, löst das Problem aber nicht auf technischer Seite.
    Eine abstrakte Deallokationsmethode ist in der Tat eine Möglichkeit, macht das Framework aber (meiner Meinung nach) nicht wirklich benutzerfreundlicher. Da das Schnittstellendesign in meinem Fall sehr wichtig ist, ist eine abstrakte Deallokationsmethode vermutlich nur der zweitbeste Weg...

    Gibt's weitere Alternativen?



  • Blackthorne schrieb:

    Eine abstrakte Deallokationsmethode ist in der Tat eine Möglichkeit, macht das Framework aber (meiner Meinung nach) nicht wirklich benutzerfreundlicher.

    Würde den Kohl aber IMHO auch nicht fetter machen.
    Eine allgemein als vernünftig anerkannte Regel besagt "Wer den Speicher anfordert, gibt ihn auch wieder frei".

    Davon abgesehen: malloc/free ist C, new([])/delete([]) C++. Musst du wirklich beides unter einen Hut bringen?



  • pock schrieb:

    Eine allgemein als vernünftig anerkannte Regel besagt "Wer den Speicher anfordert, gibt ihn auch wieder frei".

    Das Argument klingt in der Tat vernünftig und würde mir das Leben auch deutlich einfacher machen. Das dumme ist, dass der Benutzer nicht weiß, wann der Speicher wieder freigegeben werden kann. Das kann nur ich innerhalb des Frameworks entscheiden. Denn der Speicherbereich stellt einen Sendepuffer dar. Wann dieser ausgelesen wird, hängt von verschiedenen Dingen ab, die der Benutzer nicht beeinflussen kann.

    pock schrieb:

    Davon abgesehen: malloc/free ist C, new([])/delete([]) C++. Musst du wirklich beides unter einen Hut bringen?

    Ja, das wäre aus diversen Gründen nicht so schlecht.



  • Blackthorne schrieb:

    Eine abstrakte Deallokationsmethode ist in der Tat eine Möglichkeit, macht das Framework aber (meiner Meinung nach) nicht wirklich benutzerfreundlicher.

    IMHO macht es eine "free" Funktion sehrwohl benutzerfreundlicher, dann kann der Userli sich aussuchen was er verwendet, oder ob er vielleicht sogar nen statischen Block verwendet - was auch immer.

    Oder eben du schreibst "new" vor.

    Wenns allerdings um "waschechtes C++" geht hast du eine einfache 3. Möglichkeit, nämlich indem du einen boost::shared_ptr<void> zurückgeben lässt. boost::shared_ptr<> unterstützt ja netterweise "custom deleter".
    So kann der User auf seiner Seite verwenden was er will, muss keinen zusätzlichen Aufwand treiben wenn er einfach nur new/delete verwenden will (default von shared_ptr), und dich braucht es in der lib nicht kümmern was verwendet wird.



  • @hustbaer
    "waschechte C++ coder" benutzen keine STL, geschweige so etwas wie boost! die benutzen nur Klassen die sie selbst geschrieben haben und keine erweiterungen, die mal Standardisiert wurden oder hoffen das sie jemals standardisiert werdne, wie bei boost.



  • asdffdsa schrieb:

    @hustbaer
    "waschechte C++ coder" benutzen keine STL, geschweige so etwas wie boost! die benutzen nur Klassen die sie selbst geschrieben haben und keine erweiterungen, die mal Standardisiert wurden oder hoffen das sie jemals standardisiert werdne, wie bei boost.

    😮 🙄 😞
    Irony?



  • asdffdsa schrieb:

    @hustbaer
    "waschechte C++ coder" benutzen keine STL, geschweige so etwas wie boost! die benutzen nur Klassen die sie selbst geschrieben haben und keine erweiterungen, die mal Standardisiert wurden oder hoffen das sie jemals standardisiert werdne, wie bei boost.

    Aha. Gut dass du mich aufgeklärt hast. Sonst hätte ich das Zeug am Ende noch selbst verwendet, und wäre dann kein echter C++ Programmierer mehr. Dann müsste ich auf meine nächsten Bewerbungen draufschreiben "C++, aber kein echtes", und würde nie wieder einen Job bekommen. Huch. Das war ja mal knapp.

    Zur Info: ich meinte eigentlich "waschechtes C++" so wie in "C++ welches nicht noch von C aus angesprochen werden muss".



  • Blackthorne schrieb:

    pock schrieb:

    Eine allgemein als vernünftig anerkannte Regel besagt "Wer den Speicher anfordert, gibt ihn auch wieder frei".

    Das Argument klingt in der Tat vernünftig und würde mir das Leben auch deutlich einfacher machen. Das dumme ist, dass der Benutzer nicht weiß, wann der Speicher wieder freigegeben werden kann. Das kann nur ich innerhalb des Frameworks entscheiden.

    Naja, die Entscheidung wann freigegeben werden soll, kann ja das Framework treffen. Die tatsächliche Freigabe sollte dann aber der machen, der den Speicher angefordert hat. D. h. wenn eine benutzerdefinierte Allokationsfunktion gefordert wird, ist es nur konsequent, das gleiche auch für die Deallokation zu fordern.

    Du kannst innerhalb des Frameworks nicht wissen, wo der Speicher herkommt. Also kannst du ihn nicht automatisiert freigeben. Spätestens wenn der Anwender dir eine Referenz auf

    static int fooBuffer[65536];
    

    als Speicherblock liefert, kommst du weder mit delete noch mit free() weiter.



  • Danke für die vielen Anregungen.

    Ich denke, ich werde mein Problem so lösen, dass der Nutzer über eine durch meine Lib bereitgestellte Operation einen Sendepuffer anfragen kann (in der Größe, die der Nutzer benötigt). Meine Lib legt den Speicherbereich an (oder wiederverwendet einen bereits angeforderten Speicherbereich) und liefert die entsprechende Adresse zurück. Auf diese Weise kann ich den Speicher intern so verwalten, wie ich will. Außerdem kann es nicht passieren, dass der Nutzer vergisst, den Speicher wieder freizugeben. Eine Fehlerquelle weniger...

    Hört sich ja auf den ersten Blick ganz vernünftig an.



  • Du musst dem Anwender dann nur sagen wie lange garantiert ist, dass der Speicherbereich nicht freigegeben wird. Also auch wieder dokumentieren. Die Klasse std::string garantiert z.B. dass der Speicherblock, der von c_str() zurückgegeben wird existiert, solange der String existiert und sein Inhalt nicht modifiziert wird (wobei Modifikationen, die die Länge nicht ändern imho sogar erlaubt sind).



  • LordJaxom schrieb:

    Du musst dem Anwender dann nur sagen wie lange garantiert ist, dass der Speicherbereich nicht freigegeben wird. Also auch wieder dokumentieren.

    Das weiß ich genau genommen auch nicht. 🙂 Ich nehme an, dass Du Dich nicht mit MPI auskennst, aber das Problem ist, dass ich eine nicht blockierende Sendeoperation aufrufe. Das ist im Prinzip nichts anderes als eine Aufforderung an das MPI-Laufzeitsystem, eine Nachricht durchs Netzwerk zu verschicken. Wann der Sendepuffer, den ich dem Nutzer übergebe, tatsächlich vom MPI-Laufzeitsystem ausgelesen wird, ist unklar. Daher kann ich dem Nutzer auch keine Garantien geben.

    Und jetzt, wo ich diese Worte schreibe, wird mir klar, dass ich entweder eine blockierende Sendeoperation nutzen muss, die erst dann zurückkehrt, wenn die Nachricht verschickt wurde, oder aber ich muss den Puffer intern kopieren, damit der User nicht von außen in den Speicherbereich schreibt, während das MPI-Laufzeitsystem diesen ausliest.

    Wir stellen fest: Ein solches Forum ist gleich doppelt sinnvoll. Viele Dinge erklärt man sich hier selbst. 😉



  • Blackthorne schrieb:

    Und jetzt, wo ich diese Worte schreibe, wird mir klar, dass ich entweder eine blockierende Sendeoperation nutzen muss, die erst dann zurückkehrt, wenn die Nachricht verschickt wurde, oder aber ich muss den Puffer intern kopieren, damit der User nicht von außen in den Speicherbereich schreibt, während das MPI-Laufzeitsystem diesen ausliest.

    Wir stellen fest: Ein solches Forum ist gleich doppelt sinnvoll. Viele Dinge erklärt man sich hier selbst. 😉

    Wenn kopieren kein Problem ist, dann kopiere. Ist das einfachste. Dann brauchst du den ganzen Puffer quargel nurmehr intern, deine Sendefunktion nimmt dann einfach einen void* (char*, T* - was auch immer) und ein size_t. Die kopiert dann einfach alles bevor sie zurückkehrt, fertig.

    Oder aber du machst irgendwas ala (C-Style, wenn du nur ein C++ Interface brauchst sähe das natürlich eleganter aus):

    struct Buffer; // opaque, wird also nicht in einem öffentlichen Header File definiert
    
    Buffer* CreateBuffer(size_t size);
    void* GetBufferPtr(Buffer* buffer);
    size_t GetBufferSize(Buffer* buffer);
    int IsBufferBusy(Buffer* buffer); // != 0 wenn er noch vom System verwendet wird, sonst 0
    void WaitBuffer(Buffer* buffer); // wartet auf IsBufferBusy() == 0
    void FreeBuffer(Buffer* buffer); // hau weg den Scheiss
    

    Oder, noch eine andere Möglichkeit: du verlangst vom Client einfach dass er sich für jedes "send" einen neuen Buffer vom der Library holt - dann kann auch nie unklar sein wann der Buffer "wiederverwendet" werden darf, da er nie wiederverwendet werden darf. Dazu gibts dann natürlich eine "Buffer den ich doch nicht brauce wieder freigeben" Funktion, für Fälle wo der Client aus irgendeinem Grund einen Buffer angefordert hat, aber den doch nicht abschicken will.

    Am einfachsten (für den User) ist natürlich die "Daten kopieren" Variante, bei den anderen beiden kann es schnell passieren dass unachtsame oder einfach zu doofe/konfuse User Fehler machen, die dann schwer zu finden sind.

    Nochwas: die "Daten kopieren" Variante lässt sich kaum bzw. garnicht mit einer "non-blocking Garantie" verbinden. Deine Library kann ja nicht unendlich viel Speicher anfordern. Die einzigen 2 Möglichkeiten die mir dazu einfallen: entweder ala non-blocking Berkeley Sockets, d.h. "send()" liefert einfach "geht nicht" zurück wenn alle internen Buffer schon voll sind, oder aber "send()" blockiert in dem Fall dass alle Buffer voll sind einfach solange bis irgendwo was frei wird. Im 2. Fall könnte es allerdings günstig sein dem User eine Möglichkeit geben eine gewisse Mindestgrösse für die internen Buffer vorzugeben (oder vorzuschlagen) -- ob das nötig ist kommt natürlich drauf an ob die Library nicht sowieso eine in jedem Fall ausreichende/ideale Grösse selbst ermitteln kann.

    Achherrjeh, ist wieder lange geworden.
    Ich hoffe nur ich hab jetzt nicht nur Zeugs geschrieben was dir eh schon klar ist... 😃


Log in to reply