Zugriffsfehler auf "std::map" in " _Orphan_ptr(const _Nodeptr _Ptr)"



  • Hallo zusammen,

    Problem leider komplex. Versuche einfache Darstellung.
    Auszüge aus dem Quellcode kann ich kaum hier einbringen.

    Wenn ich einen Eintrag aus einer "std::map" löschen will ("map->erase(key)") oder das Programm beenden will
    (Ende des Eltern-Objekts), bekomme ich immer genannten Fehler (Zugriffsverletzung "C...0005") in der Datei "xtree",
    bei Löschen ausserhalb des Erzeugungs-Ortes der map-Inhalte.
    Manchmal auch in "_Container_base12::_Orphan_all_unlocked_v3()" (Beenden des Programms).
    Dabei stelle ich sogar fest, dass der gewünschte Eintrag aus der "std::map" schon entfernt ist.
    Crasht bei leeren und vollen "std::maps", egal, ob mit echten Daten gefüllt oder mit Verweisen!
    Bin also in einem Dilemma.

        void _Orphan_ptr(const _Nodeptr _Ptr) noexcept {	// Auszzug aus den MS-Libs
    #if _ITERATOR_DEBUG_LEVEL == 2
            _Lockit _Lock(_LOCK_DEBUG);
            _Iterator_base12** _Pnext = &this->_Myproxy->_Myfirstiter;
            while (*_Pnext) {
                const auto _Pnextptr = static_cast<const_iterator&>(**_Pnext)._Ptr;	// hier crashts
                if (_Pnextptr == _Myhead || (_Ptr != nullptr && _Pnextptr != _Ptr)) {
                    _Pnext = &(*_Pnext)->_Mynextiter;
                } else { // orphan the iterator
                    (*_Pnext)->_Myproxy = nullptr;
                    *_Pnext             = (*_Pnext)->_Mynextiter;
                }
            }
        ...
    

    Folgende Konstruktion:
    Die "std::maps" dienen zum Austausch von Daten zwischen mehreren TCP-Threads.

    Ein Anlage-Objekt kommuniziert mit einer Maschine.
    Zusätzlich verteilt es die empfangenen Daten, über Server-Threads, an weitere Clients (externe Programme),
    gemäss deren Anfragen an die Maschine. Muss also wissen wer was von der Maschine will und Antwort zuordnen.
    Dazu besitzt es einen Client-Thread zur Verbindung zur Maschine und beliebig viele
    Server-Threads zu den angeschlossenen Client-Programmen.
    Alles funktioniert auch soweit!

    Die Verteilung der Daten erfolgt über zwei "std::maps". (Eine für Datenaustausch, andere für Threadverwaltung)
    Die "std::maps" sind Member der Anlage-Klasse.
    Die Daten in den "std::maps" sind in eine "struct" gekapselt und über Verweis in der std::map.
    (Habe es auch schon mit "echten" Daten in den "structs" probiert.)
    Ihr tatsächlicher Speicher ist in den Objekten (als Member),
    die die jeweiligen Kommunikations-Threads starten oder auch wieder beenden.
    Einige Daten in den "structs" sind selbst wieder Verweise auf Objekte.
    Wird ein Thread beendet, so wird auch sein Steuerungs-Objekt beendet.
    Dabei werden die Daten in den "std::maps" natürlich ungültig,
    wobei die "std::map", mit Inhalt, selbst weiterhin vorhanden ist.
    Innerhalb des Threads, der die Daten in die "std::map" geschrieben hat,
    kann das map-Element auch wieder gelöscht werden, sowie auch die Daten darin bereinigt werden können.

    Objekte in der "struct", die während des Life-Zyklus ordentlich bereinigt wurden,
    haben nach Ende des Threads wieder ungültige Werte, wenn "std::map"-Element noch nicht gelöscht!
    Aber auch die "std::map" ist betroffen, die nur echte, einfache Daten enthält (keine Verweise oder Objekte).

    Ich habe da etwas von PDS type in C++, bzw. Plain Old C++ Object, gelesen.
    Es kommt mir aber nicht so vor, als ob da ein Zusammenhang besteht... ???

    Hier eine der beiden "structs" mit zugehöriger "std::map" mit Referenz für den Austausch zwischen den Threads:

    typedef struct _MrData
    	BOOL				bInUse;
    	pCRing				pReadRsp;
    	pCRing				pWriteRsp;
    	pCRing				pErrorRsp;
    	long				lWritePos;
    	DWORD				lWriteRspPos;
    	BOOL				bWriteToCan;
    	BOOL				bDataInWRITE;
    	HANDLE			hTimerThreadRspSleep;
    	CRITICAL_SECTION	m_csRspCriticalSection;
    } MRDATA, *PMRDATA;
    
    std::map<CString,PMRDATA> m_mapData;
    

    Die "std::maps" werden an die einzelnen Threads über Verweis durchgereicht:
    std::map<CString,PMRDATA>* m_pmapData;

    Beim Versuch einen Eintrag der "std::map" zu löschen ("erase(..)"), führe ich auch ein "delete"
    auf die in der "struct" enthaltenen Objekte aus. Das funktioniert!

    Ich habe ausserdem noch sichergestellt, dass alle "std::maps" leer sind,
    bevor das Anlagen-Objekt beendet wird.
    Das "erase(..)" geht am regulären Ende des Kommunikations-Threads, in dem auch das "insert()" gemacht wurde.

    Doch selbst die leeren "std::maps" enden beim Verlassen des Destructors des Anlagen-Objekts mit diesem Crash.
    An diesem Punkt crashet es dann dort: (egal, ob was drin steht oder nicht)

    _CONSTEXPR20_CONTAINER void _Container_base12::_Orphan_all_unlocked_v3() noexcept {	// Auszzug aus den MS-Libs
        if (!_Myproxy) { // no proxy, already done
            return;
        }
    
        // proxy allocated, drain it
        for (auto& _Pnext = _Myproxy->_Myfirstiter; _Pnext; _Pnext = _Pnext->_Mynextiter) { // TRANSITION, VSO-1269037
            _Pnext->_Myproxy = nullptr;
        }
        _Myproxy->_Myfirstiter = nullptr;
    }
    

    Hierzu noch die Aufrufliste innerhalb der STL-Struktur:

    Titan2.exe!std::_Container_base12::_Orphan_all_unlocked_v3() Zeile 1187 C++
    Titan2.exe!std::_Container_base12::_Orphan_all_locked_v3() Zeile 1042 C++
    Titan2.exe!std::_Container_base12::_Orphan_all() Zeile 1203 C++
    Titan2.exe!std::_Tree_val<std::_Tree_simple_types<std::pair<ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>> const ,_MrCanIdsMem>>>::_Erase_head<std::allocator<std::_Tree_node<std::pair<ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>> const ,_MrCanIdsMem>,void *>>>(std::allocator<std::_Tree_node<std::pair<ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>> const ,_MrCanIdsMem>,void *>> & _Al) Zeile 752 C++
    Titan2.exe!std::_Tree<std::_Tmap_traits<ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>,_MrCanIdsMem,std::less<ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>>,std::allocator<std::pair<ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>> const ,_MrCanIdsMem>>,0>>::~_Tree<std::_Tmap_traits<ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>,_MrCanIdsMem,std::less<ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>>,std::allocator<std::pair<ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>> const ,_MrCanIdsMem>>,0>>() Zeile 1088 C++
    Titan2.exe!std::map<ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>,_MrCanIdsMem,std::less<ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>>,std::allocator<std::pair<ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>> const ,_MrCanIdsMem>>>::~map<ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>,_MrCanIdsMem,std::less<ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>>,std::allocator<std::pair<ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>> const ,_MrCanIdsMem>>>() C++
    Titan2.exe!CBaseAnlage::~CBaseAnlage() Zeile 256 C++

    Hat bitte jemand eine Erklärung dafür?
    Oder vielleicht hat ja sogar jemand einen Lösungsvorschlag?



  • Hast du verschiedene Threads, die auf die maps zugreifen, von denen mindestens einer schreibend auf die map zugreift?



  • @DocShoe
    Hallo DocShoe,

    ja, genau!
    Die Threads tragen Elemente ein und ändern auch die Werte in einzelnen map-Elementen.



  • Hast du den Zugriff gegeneinander verriegelt (Critical sections, mutex)?



  • @DocShoe
    ja, natürlich. Daher auch die CritSec als struct-Element.
    Und noch andere Verriegelungen...
    Das Problem ist ja, dass es crasht, obwohl die map schon leer ist.
    ... Also auch dann. Auch wenn ich Elemente vom Elternobjekt aus löschen will
    und alle Threads, die drauf zugreifen würden schon beendet sind.
    Also echtes Dilemma.

    Es crasht also spätestens dann, wenn die maps alle leer sind und das
    Elternobjekt am Ende seines Destruktors ist.

    Ich hatte natürlich auch Zugriffskonflikte. Aber die sind dann erkennbar.
    Z.B. wenn ich ein Objekt beendet hatte, das Elemente löscht,
    aber ein Thread noch lief, der dann auf das Element zugreifen will.
    Das lässt sich alles erkennen.



  • @elmut19

    Um an die CriticalSection eines Elementes zu kommen musst du ja schon Zugriff auf die map haben, dann ist das schon zu spät. Zeig mal bitte den Code, wo du auf die beiden maps zugreifst.



  • @elmut19 sagte in Zugriffsfehler auf "std::map" in " _Orphan_ptr(const _Nodeptr _Ptr)":

    @DocShoe
    ja, natürlich. Daher auch die CritSec als struct-Element.

    Wenn du pro Eintrag eine CRITICAL_SECTION hast, dann wird das nicht funktionieren. Da verwenden dann ja unterschiedliche Threads unterschiedliche CRITICAL_SECTION und können daher trotzdem wieder gleichzeitig auf die Map zugreifen. Du musst für alle Threads die auf die selbe Map zugreifen die selbe CRITICAL_SECTION verwenden.



  • @hustbaer @DocShoe
    Das mit dem Code ist schwierig. Brauche da etwas, um vielleicht etwas zu extrahieren.

    Zu den CritSects: Jeder Thread holt sich seinen eigenen Eintrag.
    Die eine CritSec in der Struct ist dann nur für diese Element zuständig.
    Ich habe auch noch weitere übergeordnete CritSec dazu.
    Auch müsste dann das Problem verstärkt während des Betriebs auftauchen.
    Aber das Problem ist ja das Beende meines Programms.
    Und dass es sogar dann crasht, wenn ich die maps sowie die darin enthaltenen
    Objekte vorschriftsmässig "deleted" habe.



  • @elmut19
    Der Anfang der Threads, Client oder Server, folgt diesem Schema:
    Hier, der ClientThread. Der greift natürlich auf alle map-Elemente zu.
    Die ServerThreads nur auf ihre eigenen. Und dafür ist diese CritSec gedacht.
    Das funktioniert auch.
    Wie gesagt, keine Probleme während der Arbeit der Threads!

    static VOID CClientInterruptThreadT2Ng(LPVOID lpvParam)
    {	
    	CMrSocketClientThreadT2Ng* p = (CMrSocketClientThreadT2Ng *)lpvParam;
    	PMRCANIDSRAM pR = &p->m_CanPrt; // Klasse des Protokolls, enth. 3 "pCRingspeicher"-Objekte der Anlage.
    	CString csOwnMapKey =	_T("0");// Der eigene Key ist immer "0", zur Unterscheidung von den angeschl. Clients.
    	CFehlerMeldung sLastError;
    	TCPIP_PTK_T2_NG_HEADER sHeader;
    	CANMESSAGEBUF SendVersionsMsg,ReadVersionsMessage;
    	std::map<CString,PMRCANIDSRAM>::iterator itThreadRsps;
    	char*pWritedate;
    	int r = 0;
    	DWORD	rMax = 0,
    		dwLastError  = 0,
    		dwOPenNoOf = 0,	
    		dwWaitRead	= 0;
    	BOOL	bErstStart = TRUE;
    
    
    _Start:
    	//------------------------------------------------------------------------------------------------------
    	// Allgemeine Überprüfung, ob auf Thread über die "std::map" noch zugegriffen werden darf.
    	p->m_CanPrt.bInUse = TRUE; // 07.07.22: identisch mit "itThreadRsps->second->bInUse"
    	itThreadRsps = p->m_pmapSocketThreadRsps->find(csOwnMapKey);
    	if (itThreadRsps != p->m_pmapSocketThreadRsps->end()) {
    		goto _Ende;// Bestehenden Eintrag nicht überschreiben!
    	}
    	else {// Hier identifiziert csMapKey = "0" das eigene Anlage-Objekt dieser SW im Client-Thread.
    		p->m_pmapSocketThreadRsps->insert(std::make_pair(csOwnMapKey, &p->m_CanPrt));
    	}
    	//------------------------------------------------------------------------------------------------------
    	// HP, 29.06.22: Die ersten Schritte (Connect zur Anlage) müssen über eigenen Ringspeicher erfolgen.
    	//     Hierzu gehören "Öffnen des Kanals", "Variable initialisieren", "Protokoll abfragen"
    	itThreadRsps = p->m_pmapSocketThreadRsps->find(csOwnMapKey);
    	if (itThreadRsps != p->m_pmapSocketThreadRsps->end()) {
    		InitializeCriticalSection(&itThreadRsps->second->m_csRspCriticalSection);// nur eigene CS
    	}
    	//------------------------------------------------------------------------------------------------------
    
    	... Hier kommt einiges ohne Einsatz von CritSecs, wie Befehlspuffer und div. Initialisierungen.
                Dann erst die Thread-Schleife ...
    
    		while(!p->m_sMrSocketThreadDataAndState.m_iThreadAktionSoll)// Solange SOCKETCLIENTAKTION_SOLL_RUN (=0)
    		{	
    		_New:
    			if (p->m_pmapSocketThreadRsps) {
    
    				for (itThreadRsps = p->m_pmapSocketThreadRsps->begin();
    					 itThreadRsps != p->m_pmapSocketThreadRsps->end(); itThreadRsps++) {
    
    					if (TryEnterCriticalSection(&itThreadRsps->second->m_csRspCriticalSection)) {// 12.07.22: NEIN? ->gleich weiter zum nächsten map-Element (no Wait)
    
    						int iRet = itThreadRsps->second->pWriteRsp->readlinesize();// solange was im Ringspeicher, dieses an Controller senden
    						if (itThreadRsps->first != csOwnMapKey) {// 20.07.22: debug
    							int iiNoLines = itThreadRsps->second->pWriteRsp->NoOfLines();// solange was im Ringspeicher, dieses an Controller senden
    						//	TRACE(_T("CClientInterruptThreadT2Ng: Check nach Befehlen von Nr %s, vor While(pWriteRsp->readlinesize()=%d), NoOf=%d\n"), itThreadRsps->first, iRet, iiNoLines);
    						}
    ...
    
    

    Und natürlich auch das Ende eines (dieses) Threads

    
    _Ende:
        TRACE (_T("T2NG  ---------------------- ExitThread(0)\n"));// 13.07.22: auch die eigene CritSect löschen!
    	int iHelp;
    	//EnterCriticalSection(p->m_pcsSocketThreadsEndCriticalSection);		// 10.08.22: auskommentiert, da es mit anderem "close()" kollidiert.
    	itThreadRsps = p->m_pmapSocketThreadRsps->find(csOwnMapKey);
    	if (itThreadRsps != p->m_pmapSocketThreadRsps->end()) {
    		itThreadRsps->second->bInUse = FALSE;	// setzt damit auch pR->InUse auf FALSE.
    		if (itThreadRsps->second->pErrorRsp) {
    			itThreadRsps->second->pErrorRsp->deinit();
    			delete itThreadRsps->second->pErrorRsp;
    			itThreadRsps->second->pErrorRsp = NULL;
    		}
    		if (itThreadRsps->second->pReadRsp) {
    			itThreadRsps->second->pReadRsp->deinit();
    			delete itThreadRsps->second->pReadRsp;
    			itThreadRsps->second->pReadRsp = NULL;
    		}
    		if (itThreadRsps->second->pWriteRsp) {
    			itThreadRsps->second->pWriteRsp->deinit();
    			delete itThreadRsps->second->pWriteRsp;
    			itThreadRsps->second->pWriteRsp = NULL;
    		}
    		DeleteCriticalSection(&itThreadRsps->second->m_csRspCriticalSection);
    		itThreadRsps = p->m_pmapSocketThreadRsps->end();
    		TRACE(_T("T2NG  ---------------------- before ExitThread(0): std::mapRsp->erase(Key)\n"));// 13.07.22: auch die eigene CritSect löschen!
    		iHelp = p->m_pmapSocketThreadRsps->erase(csOwnMapKey);vorhanden
    	}
    
    


  • Jau, da haben wir doch schon das Problem.
    In Zeile 23 greifst du ohne Verriegelung auf die map zu, das könnte der Grund für den Fehler sein.

    Du musst in beiden Threads

    1. vor dem Zugriff auf die map den Zugriff verriegeln
    2. dir den iterator holen
    3. Sachen mit dem iterator tun, bis er nicht mehr gebraucht wird
    4. die Verriegelung wieder aufheben


  • Gibt es bei der Anwendung einen Performancehintergrund? Sonst könnte man ja prinzipiell auch eine CritSect um die ganze Map ziehen, anstatt um einzelne Einträge.

    Wobei meiner Erfahrung nach, zumindest bei unseren Projekten, die Performance erst dann stimmt, wenn man generell auf CriticalSections verzichtet und auch die Struktur der Multithreading-Anwendung komplett auf LockFree umstellt.



  • @DocShoe
    Das (Zeile 23, erster Codeteil) kann es eigentlich nicht sein.
    Es müsste dann ja an dieser Stelle krachen.
    Die map muss zwangsläufig gleichzeitig gelesen und bearbeitet werden,
    sonst funktioniert die ganze Sache nicht.
    Und der Datenaustausch über diese map funktioniert ja, mit mehreren Threads.

    Nur die einzelnen map-Elemente dürfen nicht gleichzeitig bearbeitet werden.

    Ich kann mir eine CritSec nicht als Grund für dieses (prinzipielle??) Problem vorstellen.



  • @It0101
    Hallo lt0101
    Performance brauchts immer, auch hier. Sehr wichtig.
    Aber die Absicherung durch CritSecs brauche ich auch.
    Habs während des Aufbaus auch ohne probiert.

    Selbst wenn es nicht crashen würde, könnte ich ohne nicht sicherstellen,
    dass auf eine Anfrage von client-Programm A an die Anlage auch die
    zugehörige Antwort genau an diesen Client zurück kommt.

    Eine Frage-Antwort Aktion muss immer komplett abgeschlossen sein,
    bevor man zur nächsten gehen kann. Und die ServerThreads, die das verteilen,
    müssen das eingephast sein.



  • @elmut19
    Ich habe mir angewöhnt, komplett auf CritSects zu verzichten, was auch funktioniert. Man arbeitet stattdessen mit Queues ( lock-free ) und versendet die Daten mittels Q an denjenigen, der sie braucht. Somit gibt es keine Zugriffe mehr auf den Datenhalter ( z.B. Storage ). D.h. jeder der die Daten braucht bekommt gewissermaßen eine Kopie, teilweise auch mit shared_ptr, wo das möglich ist. In meinem neuen Projekt bekommt jeder ClientThread alle Daten und sortiert selber aus, was er davon braucht. Dadurch gar kein Verwaltungsaufwand und null critsects. Funktioniert in meinem Anwendungsfall hervorragend und ist superschnell.

    Daher für meine Anwendungsfälle mein Fazit: CritSects sind der Tod für die Performance.



  • @DocShoe
    Es crasht aus folgenden Gründen:

    1. Beim Löschen eines map-Elements von einem Ort aus,
      der dieses Element nicht reingesetzt hat
      und zusätzlich der Thread schon beendet ist, auch wenn
      Objekte innerhalb dieses map-Elements ordnungsgemäss bereinigt wurden.
    2. Wenn ich versuche die ganze Anwendung zu beenden.
      Und da am Ende des Destruktors des Objekts, das diese maps als Member enthält
      und auch die ganzen Threads startet und beendet.
      Und das passiert obwohl ich dafür gesorgt habe, dass die maps alle leer sind.
    3. Es gibt noch diverse Zwischen-Möglichkeiten, die ebenfalls zum Absturz führen.

    Zu 1: Wenn ein Thread beendet wurde, so enthält das zugehörige map-Element
    ungültige Daten. egal ob diese vorher bereinigt und genullt wurden oder
    der Thread rücksichtslos gekillt wurde. Es sei denn der Thread hat sein
    map-Element selbst gelöscht.
    zu 2: Der Punkt ist für mich völlig unlogisch, nachdem ich es ja geschafft habe,
    die Elemente irgendwie zu löschen.



  • @It0101
    Dann wäre es der Ansatz, auf maps für den Datenaustausch gänzlich zu verzichten.
    Da weiss ich nicht, wie ich es realisieren kann.

    Wie man vielleicht erahnen kann, handelt es sich um eine Erweiterung einer bestehenden SW.
    Da war dieser Schritt der naheliegendste.

    Wie schon gesagt.
    Ich sehe nicht den Grund in den CritSecs, sondern in den maps selbst.
    Aber ich kenn den Grund nicht.



  • @elmut19 sagte in Zugriffsfehler auf "std::map" in " _Orphan_ptr(const _Nodeptr _Ptr)":

    std::map<CString,PMRDATA> m_mapData;

    Mal eine Frage. Warum nutzt du hier PMRDATA und nicht MRDATA?



  • @It0101
    Es gibt natürlich für dies Art der Anwendung verschiedene Design-Ansätze.
    Man könnte anderen Client-Programmen über RPC den Zugriff auf gespeicherte
    Daten geben.
    Diese SW hat nun mal ein Design, das einen TCP-Thread verwendet, der mit
    der Verarbeitung der Daten einen schon sehr aufwendigen Aufbau hat.
    Hier war diese Variante noch die einfachste und logischste.

    Über Queues habe ich mir hier noch keine Gedanken gemacht, da eben im Hintergrund
    doch ein recht komplexer Aufbau mit Read- und Write-Puffer, etc. steckt, um damit
    die Asynchronität zwischen dem Bedienteil und der Kommunikation zu gewähren.
    Das alles lies sich recht einfach (naja...) in bestehendes Design integrieren.
    ...Einfacher als alle andere.



  • @Quiche-Lorraine
    Hallo Quiche-Lorraine,
    Tja, ich habe bereits beides versucht (PMRDATA und nicht MRDATA?).
    Und beides endete im selben Dilemma.

    Seit vier Wochen versuche ich alle möglichen Varianten,
    die mir bisher eingefallen sind, ausser, auf maps zu verzichten.

    Wäre der ganze Hintergrund einfacher zu beschreiben,
    und ich könnte mich auf ein paar Codezeilen beschränken,
    hätte ich schon früher im Forum nachgefragt.



  • Ich habe ganz oben in meiner Anfangs-Formulierung noch die Aufrufliste innerhalb der STL ergänzt.
    Die Tatsache, dass hier alles in einer "_Orphan_XXX" Function endet, sagt mir, dass die Software glaubt,
    dass hier "verwaiste" Objekte irgendwo rumliegen, obwohl das (zumindest meiner Meinung nach)
    und zumindest für einige Fälle nicht der Fall sein kann.


Anmelden zum Antworten