statische Speicherallokation



  • Hallo und guten Abend

    Ich hab ein kleines Problem mit der Speicherallokation bzw. mit der Funktionsübergabe (Function cSplit). Sobald ich die Funktion verlasse hat die Variable 'out' keinen Wert mehr.
    Wie könnte man dieses Problem lösen?

    Danke im Voraus.

    wachs

    Bemerkung: Bitte keine dynamische Speicherallokation. Wenn möglich möchte ich auf diesem Weg bleiben.

    #include "stdafx.h"
    #include <iostream>
    #include <string>
    #include <vector>
    #include <algorithm>
    #include <iterator>
    
    bool cSplit(const char *c1,const char *delemit, char **(&out));
    std::vector<std::string> &split(const std::string &sTxt, std::string sDe, std::vector<std::string> &sArr);
    std::vector<std::string> split(const std::string &s, std::string delim);
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	//Split-Variante mit String
    	//*************************
    	std::string sText="Trenn mir diesen Satz!";
    	std::string sDelim=" ";
    	std::vector<std::string> sResult = split(sText,sDelim);
    
    	//Split-Variante mit Char*
    	//************************
    
    	//char *cText="Trenn::mir::::diesen::Satz!::";	//So gehts noch nicht
    	char *cText="Trenn::mir::diesen::Satz!";
    	char *cDelim="::";
    	char **cResult=0;
    	if(cSplit(cText,cDelim,cResult))
    		std::cout<<"Hat funktioniert ... "<<std::endl;
    	return 0;
    }
    
    bool cSplit(const char *cText,const char *cDelimiter, char **(&out)){
    	//Umweg über die Stringvariante
    	if(std::strlen(cText)>0)
    		if(std::strlen(cDelimiter)>0){
    			std::string sText(cText);
    			std::string sDelimit(cDelimiter);
    			std::vector<std::string>sResult=split(sText,sDelimit);
    
    			std::vector<char*> cArr;
    			std::transform(std::begin(sResult), 
    						   std::end(sResult),
    						   std::back_inserter(cArr),
    						   [](std::string& s){ return &s[0]; });
    
    			static char** cOut=0;   //<< Hier ist das Problem !!!
    			out=cOut;
    			cOut=cArr.data();			
    			return true;
    		}
    		return false;
    }
    std::vector<std::string> &split(const std::string &sTxt, std::string sDe, std::vector<std::string> &sArr) {
        std::string sItem;
    	int iPos1=0,iPos2=0;
    	if(sTxt.length()>0)
    		if(sTxt.length()>0)
    			while(sTxt.find(sDe,iPos1)>=0)
    				if(iPos2>=0){
    					iPos2=sTxt.find(sDe,iPos1);
    					sItem=sTxt.substr(iPos1,iPos2-iPos1);			
    					sArr.push_back(sItem);
    					if(iPos2<0)break;
    					iPos1=iPos2+sDe.length();
    				}
    	return sArr;
    }
    std::vector<std::string> split(const std::string &sTxt, std::string sDe) {
        std::vector<std::string>sArr;
        split(sTxt,sDe,sArr);
        return sArr;
    }
    


  • wachs schrieb:

    Sobald ich die Funktion verlasse hat die Variable 'out' keinen Wert mehr.

    Du weist doch selber 0 zu. Worüber wunderst du dich?



  • Hallo manni66

    Ja Anfangs schon, aber nachher füll ich ja die Variable, und der entsprechende Wert sollte auf die Variable übertragen werden. Das sollte so schon funktionieren.

    Was aber nicht funktioniert ist die Übergabe nach erlassen der Funktion.

    wachs



  • wachs schrieb:

    ...
    und der entsprechende Wert sollte auf die Variable übertragen werden.

    Mit Magie? Normalerweise werden Zuweisungen genommen, da ist aber keine.



  • ich habs jetzt kurz ausprobiert. Zuerst die Zuweisung "cOut=cArr.data();" und nachher "out=cOut;". Funktioniert trozdem nicht.

    Auch wenn ich den Anfangswert beilege "out=&cOut[0];" funktionierts nicht.
    Hab wieder den gleichen Effekt. In der Main besitzt die Variable cResult keinem wert.



  • @wachs

    bool cSplit(const char *cText,const char *cDelimiter, char **(&out)){
        //...
                static char** cOut=0;   //<< Hier ist das Problem !!! 
                out=cOut; 
                cOut=cArr.data();      
                return true;
    

    Beschreib mal Schritt für Schritt was der Code deiner Meinung nach machen sollte.
    Und zwar genauestens. Nicht so als ob du es jemandem erklären würdest der gleich viel oder mehr vom Programmieren versteht wie du, sondern so als ob du es jemandem mit dem IQ von inetwa einem Quadratmeter Rigipsplatte erklären müsstest.



  • Ich denke in etwa so:

    static char** cOut=0;
    Deklaration und Definition von cOut=0.
    Gleichzeitig Speicherreservierung im statischen Speicher durch die variable cOut

    cOut=cArr.data();
    Zuweisung der Daten von cArr in die Variable cOut

    out=cOut;
    Adressweitergabe an die Variable out.

    return true;
    Direktes rausspringen aus der Funktion mit bool true



  • wachs schrieb:

    cOut=cArr.data();
    Zuweisung der Daten von cArr in die Variable cOut

    out=cOut;
    Adressweitergabe an die Variable out.

    Warum ist hier die Reihenfolge anders als im Programm?
    Warum ist das Eine eine Zuweisung, das Andere aber eine Adressweitergabe (was soll das überhaupt bedeuten)?



  • wachs schrieb:

    ...

    Jetzt noch einmal mit den genauen Datentypen, also

    static char** cOut=0;
    

    Statischer pointer auf einen pointer auf char wird im 0 initialisiert.



  • Das wäre mein Ziel wenn ich die Adresse weitergeben könnte. Aber es will einfach nicht funktionieren.



  • @ Marthog

    Ja das habe ich rausgenommen stattdessen

    static char** cOut;
    cOut=cArr.data();
    out=cOut;
    


  • @wachs
    Naja, gerade eben so nicht.
    Du verwendest da lauter Wörter von denen keiner wissen kann was damit genau gemeint ist. Vermutlich nichtmal du selbst. Und genau das ist denke ich das Problem.

    wachs schrieb:

    Ich denke in etwa so:

    static char** cOut=0;
    Deklaration und Definition von cOut=0.
    Gleichzeitig Speicherreservierung im statischen Speicher durch die variable cOut

    Was bedeutet "Speicherreservierung"? Wie viel Speicher?

    wachs schrieb:

    cOut=cArr.data();
    Zuweisung der Daten von cArr in die Variable cOut

    Was sind "die Daten von cArr"?
    Was wird kopiert? Wie viel Byte? Welcher Wert?

    wachs schrieb:

    out=cOut;
    Adressweitergabe an die Variable out.

    Was soll Adressweitergabe bedeuten?



  • @ hustbaer

    Ich kann dir nur recht geben. Leider sind mir die Begriffe ein bisschen fremd.

    Aber ich denke auch wenn ich diese bestens kennen würde, hätte ich so das Problem wahrscheinlich noch nicht gelöst 🙂



  • Müsste man hier den wirklich auf eine Variante wie Malloc ausweichen?. Das würde doch heissen, ich müsste die Variable nachher wieder selber zurücksetzen, und das möchte ich eigenlich vermeiden.

    Gibt es keine Möglichkeit die Daten direkt in die Variable cResult einfliessen zu lassen?



  • Dein Problem ist (zunächst) nicht malloc, sondern dass du durch die ** Programmierung nicht durchblickst. Dabei kennst und benutzt du die Lösung ja schon: std::vector und std::string. Warum willst du das mit aller Gewalt nach char** verbiegen?



  • @wachs
    Wenn du keine dynamische Speicheranforderung willst, dann musst du den Aufrufer den Puffer für das Ergebnis bereitstellen lassen.

    Aber mal 5 bis 42 Schritte zurück: was willst du eigentlich machen? Und wieso meinst du es so machen zu wollen?
    Erklär mal.



  • Ich werde mich eventuell morgen abend nochmals damit befassen können.

    Ich hab gehofft, dass es eine Möglichkeit gibt ohne dynamische Speicheranforderung.

    Es geht mir nur um das genaue Verstehen der Funktionsübergaben im C/C++. UNd dazu gehört auch die Speicherallokation. Man kann in Bezug auf dieses Thema tonnenweise Seiten lesen, aber wirklich gute Erklärungen habe ich bis jetzt in keinem Turtorial oder Buch gefunden.

    Das ist der Grund.

    Gruss wachs


  • Mod

    wachs schrieb:

    Es geht mir nur um das genaue Verstehen der Funktionsübergaben im C/C++. UNd dazu gehört auch die Speicherallokation.

    In C vielleicht. In C++ reicht dir doch für den Anfang string und vector, die im Hintergrund alles nötige automatisch erledigen. Später kannst du dir dann angucken, wie es auch von Hand geht. Und ein gutes Vorbild, wie man es von Hand machen sollte, sind eben string und vector. Die fallen ja nicht vom Himmel, die sind auch irgendwo programmiert worden und du kannst dir deren Code angucken (ist aber nicht zu empfehlen, da Code in der Standardbibliothek komischen Namenskonventionen folgen muss und oft hochoptimiert ist, was ihn entsprechend unleserlich macht. Aber man kann sich Musterimplementierungen ansehen, bei denen Wert auf Lesbarkeit gelegt wurde)

    In C geht es leicht anders. Im Prinzip hat man in C zwei bis drei Möglichkeiten:
    -Einfach statisch ein Array einer gewissen Größe machen und hoffen, dass dies einerseits nicht zu viel Speicher ungenutzt verschwendet und andererseits aber genug für alle Zwecke ist.
    -Klassisch mit malloc und free und viel Handarbeit.
    -Wir begrüßen unsere neuen objektorientierten Oberherren und programmieren string und vector nach. Da in C aber die schönen Automatismen fehlen, die in C++ alles nötige im Hintergrund erledigen, müssen wir den Programmierer verpflichten, alles immer sauber frei zu geben. Das klingt schlimmer als es ist, denn bei manuellem malloc/free musste er das auch schon, aber nun ist besser formalisiert, welche Funktionen wo aufgerufen werden müssen.

    C und C++ sind zwei ganz unterschiedliche Sprachen. Zwar mit ähnlicher Syntax, aber mit ganz verschiedenen Philosophien, wie man an Probleme ran geht. Versuch nicht, beide gleichzeitig zu lernen!



  • Boah, wieso muss jeder Programmierer eigentlich erst einmal auf etwas positives prüfen und dann Code ausführen? Warum prüft man nicht auf etwas negatives und kehrt dann erst mal zurück? Das macht den Code übersichtlich, man spart sich etliche Einrückungen, und Leute wie ich werden arbeitslos. 😉

    bool cSplit(const char *cText,const char *cDelimiter, char **(&out))
    {
        if(std::strlen(cText)<=0
        || std::strlen(cDelimiter)<=0)
            return false;
    
        std::string sText(cText);
        std::string sDelimit(cDelimiter);
        std::vector<std::string>sResult=split(sText,sDelimit);
    
        std::vector<char*> cArr;
        std::transform(std::begin(sResult),
            std::end(sResult),
            std::back_inserter(cArr),
            [](std::string& s){ return &s[0]; });
    
        /*Warum hier static? Warum? Erklärt es mir, warum? Damit machst du keine
        **statische Speicherallokation, damit machst du einen Zeiger, der in allen
        **Funktionsaufrufen gleich ist. Und damit machst du die Funktion
        **nicht-reentrant.
        **Und als ob das noch nicht genug gewesen wäre ... schauen wir uns mal
        **die Beschreibung von data() an:
        **
        **"Returns a direct pointer *to the memory array* used *internally* by
        **the vector to store its owned elements."
        **
        **Sprich: Du machst einen Pointer cOut, der zuerst NULL hat. Das weißt du dann
        **out zu. out hat den Wert NULL. Dann weißt du cOut einen Speicherbereich
        **zu, der NACH DEM BEENDEN DER FUNKTION NICHT MEHR GÜLTIG IST, WEIL DAS
        **OBJEKT ZERSTÖRT WIRD. Das ist Jürgen Wolf, wie er im Buche steht. Und out
        **ist weiterhin NULL, weil der Wert nie zugewiesen wird. Weil das zwar 'ne
        **Referenz ist, aber keine auf cOut. Sondern auf den Zeiger, den du in
        **_tmain übergibst. Und dass das Objekt zerstört wird, wird auch nicht
        **dadurch verhindert, dass du einen statischen Zeiger auf das interne
        **Objekt hast. Wir sind hier doch nicht bei Sun/Oracle zuhause.*/
        static char** cOut=0;
        out=cOut;
        cOut=cArr.data();          
        return true;
    }
    
    std::vector<std::string> &split
    (
        const std::string &sTxt,
        std::string sDe,
        std::vector<std::string> &sArr
    )
    {
        std::string sItem;
        int iPos1=0,iPos2=0;
    
        /*Und warum doppelte Prüfung? Sollte das nicht eher sDe.length() sein?*/
        /*if(sTxt.length()>0)*/
        if(sTxt.length()<=0)
            return sArr;
    
        /*Und warum wird hier der Schleifenkörper nicht mit geschweiften Klammern
        **angegeben, und warum machst du zweimal sTxt.find?*/
        while((iPos2=sTxt.find(sDe,iPos1))>=0)
        {
            /*Sinnlos. Anfangs ist iPos2 ja bereits auf 0 gesetzt, und unten
            **prüfen wir bereits, ob wir den Wert finden konnten. Deswegen machen
            **wir auch oben im Schleifenkopf die Zuweisung.*/
            /*if(iPos2<0)
                continue;*/
    
            /*Und weil wir bereits find gemacht haben, brauchen wir den Call hier
            **nicht mehr.*/
            /*iPos2=sTxt.find(sDe,iPos1);*/
    
            /*Und hier machst du wieder keine Prüfung. Was, wenn iPos2 direkt
            **nach der Zuweisung DOCH wieder -1 ist? -1 - iPos1, das in einen
            **size_t konvertiert ergibt 0xffffffff (auf 32 Bit-Systemen) -iPos1
            **... ist die Frage, ob substr nur auf npos (exakt -1) prüft und dann
            **nur soviele Zeichen zurückgibt, wie der String hat, oder ob die 
            **Funktion bei z.B. -2 keine Längenprüfung mehr macht. Bin kein C++-
            **Experte hier, in C wäre das aber ein klassischer Overflow.*/
            sItem=sTxt.substr(iPos1,iPos2-iPos1);
            sArr.push_back(sItem);
    
            /*Jetzt können wir darauf prüfen.*/
            if(iPos2<0)
                break;
    
            iPos1=iPos2+sDe.length();
        }
        return sArr;
    }
    

    Das habe ich durch oberflächliches Lesen bemerkt. Kann sein, dass da noch mehr drinsteckt. Wie geschrieben, ich bin kein C++-Experte.

    @SeppJ: +(size_t)-1. 🙂 Auch, wenn ich widerspreche, dass der Code hochoptimiert ist, wenn mein eigenes sprintf so ungefähr dreimal schneller als die glibc-Version ist.

    Einen Weg hast du vergessen: Zuerst eine Funktion, die die Anzahl der Elemente, die vorhanden sind, berechnet, und die Zahl dann alloca übergeben. alloca reserviert zur Laufzeit Speicher auf dem Stack, was du wohl als "statischen Speicher" kennst. Problem ist, dass der Stack nicht so groß ist und bei größeren Programmen schnell mal ein Stack Overflow passieren kann. Aber das bemerkst du schon - alloca gibt nämich nicht an, ob es einen Fehler gibt. Sprich, dann ist es schon zu spät und den Programm schmiert ab. Außerdem ist es blöd, wenn du zweimal den gleichen Code ausführen musst, aber beim ersten Mal machst du nur Längenprüfung. Es gibt Ausnahmen von dieser Regel, dieser Fall ist keine.

    Ein weiteres Problem: man SOLLTE alloca niemals in einer Schleife ausführen, es sei denn, der Code ist in einer eigenen Funktion und NICHT inlined. Weil man den Speicher von alloca nicht wieder explizit freigeben kann. Wie bei free/delete halt. Bei Funktionsrückkehr passiert das automatisch. In einer Schleife nicht. Das lädt zu einem Stack Overflow praktisch ein. Und es hat überhaupt gar nichts mit C++ zu tun, wie man es verwenden sollte. Wenn du's dennoch so machen willst, wirf C++ am Besten weg und schau dir C an.

    Das Windows-Äquivalent zu alloca ist übrigens _alloca .


  • Mod

    alloca ist halt nicht Standard (auch wenn es auf vielen Plattformen existiert), daher habe ich es nicht erwähnt.

    Ist dein eigenes sprintf denn 100% gleichwertig zu dem der Standardbibliothek? Falls ja: Respekt. Die Ausgabefunktionen der Standardbibliothek sind etwas lahm, eben weil sie derart mächtig sind. Ganz besonders in C++. Ist in den Augen vieler Leute ein Designfehler, da man in 99.999% aller Fälle dafür bezahlt, dass man irgendwas auch prinzipiell mit einer von rechts nach links schreibenden, mit römischen Zahlen arbeitenden, 23-Bit variable-length Unicode, mit dem Maya-Kalender arbeitenden ,plenkenden, altchinesischen Locale ausgeben könnte, dies aber eigentlich nie nutzt.

    @SeppJ: +(size_t)-1. 🙂

    Danke, gleichfalls.


Anmelden zum Antworten