Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

Gotbread

Alter Hase

  • »Gotbread« ist der Autor dieses Themas

Beiträge: 421

Beruf: Student (Etechnik) + Hiwi

  • Private Nachricht senden

1

29.06.2009, 23:34

std::string -> bad_alloc. bug?

Hallo leute

heute bin ich über ein doch sehr seltsames problem gestolpert.

(gleich vorweg, ich verwende visual c++ standard-edition)

ich habe eine virtuelle methode

C-/C++-Quelltext

1
2
3
4
5
template <class T>
void CTextelement<T>::SetText(const std::string &newtext)
{
    text = newtext; // "text" ist ein std::string

}


in einer template-klasse. diese hat nur 2 methoden, get und settext
und dient als basisklasse für andere elemente

eigentlich kein problem. die fertige exe läuft normal. dann habich
das ganze als DLL kompiliert (inklusive export, namemangling und alles)
und in einem zweitem projekt eingebunden.

doch kann ich dann ganz normal ohne probleme / crashs eine instanz
einer abgeleiteten klasse aufstellen und auch munter methoden
von anderen basisklassen benutzen (ich komme hier nicht um mehr-
fachvererbung herrum).

jetzt rufe ich as teil auf

C-/C++-Quelltext

1
button1->SetText("Hallo");


sollte ja eigentlich funktionieren. ZACK, BUMS -> bad_alloc
durch das debugging habe ich festgestellt, das die klasse ein paar
gigabytes anfordern wollte. hoppala ganz schön viel für das kleine hallo.
(es waren glaubich 0xcccccccc bytes, bei dieser zahl sollte es klingeln)

anmerkung: der string wird richtig erstellt, bei der zuweisung in der
methode crasht es.

der debugger des vc zeigt einem immer an, welcher text in dem string
steht. solange ich nicht in der methode bin steht auch korrekt "Hallo" drin
und ich kann das inspektionsfenster "aufklappen".

so, in die methode gesprungen und der text (const referenz) wurde zu
"ÍÍÍÍHallo". viele sollten dieses 'Í' schon mal gesehen haben. die Is sehen
mir sehr nach 0xcccccccc aus.

klare sache dachte ich, der parameter wird um 4 bytes verrutscht sein
und deshalb stimmt die größe nicht mehr. erstaunlicherweise ist die
addresse des strings (was mich so wundert) vor dem aufruf und in dem
aufruf dieselbe, was an sich nicht gehen kann.

(an die addresse des wertes in der methode bin ich mit dem
"schnellüberwachen" fenster gekommen, durch den ausdruck
"(void *)&newtext", da das fenster bei "&newtext" nur den kaputten
text anzeigt [auch komisch])

was kann das sein?

die beiden codestücke (DLL und exe) wurden ein paar min. hintereinander
kompiliert, verschiedene versionen von std::string sind also sehr
sehr unwahrscheinlich.

bisher dachte ich mir:

- zeigerproblem bei MI, aber die methode wird korrekt aufgerufen
und ich kann (den einen) lokalen member inspizieren.

- bug im "schnellüberwachen" fenster, das einfach den wert des aufrufes
zwischenspeichert statt neu auszuwerten.

- bug im kompiler ??? (0.000001%)

mehr code kann ich euch leider nicht geben, aber ich versuche morgen
eine minimalversion zu reproduzieren hochzuladen (oder ein video)
Mfg Goti
www.gotbread.bplaced.net
viele tolle spiele kostenlos, viele hardware-basteleien :)

"Es ist nicht undicht, es läuft über" - Homer Simpson

the[V]oid

Alter Hase

Beiträge: 775

Wohnort: Aachen

  • Private Nachricht senden

2

29.06.2009, 23:40

Bin mir nicht sicher, aber könnte es etwas damit zu tun haben, dass man keine Tempates durch DLLs exportieren kann?
<< an dieser Stelle ist eine Signatur verstorben >>

Gotbread

Alter Hase

  • »Gotbread« ist der Autor dieses Themas

Beiträge: 421

Beruf: Student (Etechnik) + Hiwi

  • Private Nachricht senden

3

30.06.2009, 00:10

das template wird nur DLL intern benutzt.

damit das design funktioniert und die virtuellen methoden durch die
implementationen ersetzt werden und nicht umgekehrt (mieser fehler)

gebe ich der klasse per template an wovon sie erbt

C-/C++-Quelltext

1
2
3
4
5
class basisklasse1 {};
template <class T>
class textelement : public T {};

basisklasse1 *p = new textelement<basisklasse1>;


jetzt wo ich drüber nachdenke, war glaubich CElement (eine basisklasse)
die klasse die per template von Textelement erbte, aber das tut ja nichts
zur sache.

die exe arbeitet nur mit den interfaces. (was der vc trotzdem zurück-
verfolgen kann :D )
Mfg Goti
www.gotbread.bplaced.net
viele tolle spiele kostenlos, viele hardware-basteleien :)

"Es ist nicht undicht, es läuft über" - Homer Simpson

Gotbread

Alter Hase

  • »Gotbread« ist der Autor dieses Themas

Beiträge: 421

Beruf: Student (Etechnik) + Hiwi

  • Private Nachricht senden

4

30.06.2009, 12:10

habe mich nochmal durch die klassen gewühlt.


(Link)


(schwarzer pfeil: vererbung)
(oter pfeil: virtuelle vererbung)

die entsprechenden C++ deklarationen:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// erweitert IUnknown um eine methode (interface)

struct Refcounting : public IUnknown


// ein basiselement (interface)

struct Element : virtual public Refcounting


// unabhängiges interface

struct Textelement
{
    virtual void SetText(const std::string &) = 0;
};

// button-interface

struct Button : public Element, public Textelement



////// IMPL




// CRefcounting implementiert Refcounting


template <class T>
class CRefcounting : virtual public Refcounting, public T


// CElement implementiert Element


template <class T>
class CElement : public CRefcounting<T>


// das textelement


template <class T>
class CTextelement : public T


// der button

class CButton : public CTextelement<CElement<Button> >


wem das komisch erscheint:

wenn jede klasse direkt von ihrem interface erbt, wird die implementation
von den pure-virtual funktionen überschrieben
und nicht umgekehrt.
(ka warum)

darum werden die interfaces vor den implementationen
(per template) vererbt. sieht komisch aus aber funktioniert.

das debugging brachte mich nicht weiter, aus der disassembly kann man
entnehmen, das die funktion korrekt aufgerufen wird, was bei
polymorphie ein indiz für die richtigkeit ist. ebenfalls kann man entnehmen,
dass die refernez (intern nur ein pointer) nicht verändert wird:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
    49:         std::string h("hallo");
    50:         const std::string *ptr = &h;
    51:         const void *vptr = ptr;
    52:         b->SetText(h); 
mov         esi,esp 
lea         eax,[ebp-64h]           // addresse des (testweise) lokal angelegten strings holen

push        eax                     // und auf den stack pushen. wert ist hier 0x0013fea8

mov         ecx,dword ptr [ebp-3Ch] // vtbl

add         ecx,8                   // vtbl

mov         edx,dword ptr [ebp-3Ch] // vtbl

mov         eax,dword ptr [edx+8]   // vtbl

mov         edx,dword ptr [eax]     // vtbl

call        edx                     // vtbl - call


das die addresse stimmt habe ich manuell mit einem cast auf void *
kontrolliert. in dem string steht immernoch "hallo"

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
  6155:             template <class T>
  6156:             void CTextelement<T>::SetText(const std::string &newtext)
  6157:             {
  6158:                 text = newtext;
mov         eax,dword ptr [esp+4]  // eax ist immernoch 0x0013fea8

push        0FFFFFFFFh 
push        0    
push        eax  
add         ecx,78h 
call        100014C0 
  6159:             }
ret         4


der debugger meldet, sobald ich in der methode bin, dass "newtext"
= "ÌÌÌÌhallo" ist.

was ich auch nicht verstehe, sind die 0xffffffff, die ja hardcoded sind.
Mfg Goti
www.gotbread.bplaced.net
viele tolle spiele kostenlos, viele hardware-basteleien :)

"Es ist nicht undicht, es läuft über" - Homer Simpson

dot

Supermoderator

Beiträge: 9 757

Wohnort: Graz

  • Private Nachricht senden

5

30.06.2009, 16:32

Zitat von »"Gotbread"«

wenn jede klasse direkt von ihrem interface erbt, wird die implementation
von den pure-virtual funktionen überschrieben
und nicht umgekehrt.
(ka warum)


Kannst du das mal etwas genauer beschreiben, das klingt irgendwie ungut.
Wie und Woher kommen eigentlich die Instanzen der Interfaces?
Was und Woher kommt das b in b->SetText(h); aus deinem Beispielcode da oben?

Gotbread

Alter Hase

  • »Gotbread« ist der Autor dieses Themas

Beiträge: 421

Beruf: Student (Etechnik) + Hiwi

  • Private Nachricht senden

6

30.06.2009, 17:30

nun ich hatte zuerst folgende struktur

Quellcode

1
2
3
4
5
[refcounting]  ->  [element]  -> [button]
    |                  |              |                      DLL interface
____|__________________|______________|_______________
    |                  |              |                    implementation
[crefcounting] -> [celement] -> [cbutton]


ist ja auch naheliegend.

dummerweise gibt es jetzt 2 mal die funktionen von refcounting.
(aus der sicht von celement)
anstatt die implementierten methoden der crefcounting-klasse
zu erben, hat er sich für die abstrakten methoden aus dem interface
entscheiden. das gab natürlich linkerfehler. das zieht sich durch
alles klassen durch.

die instanzen werden natürlich von den konkreten klassen erzeugt
und dann über ihr interface geliefert:

C-/C++-Quelltext

1
2
3
4
5
6
Button *CWindow::AddButton()
{
    Button *b = new CButton(this);
    childs.push_back(b);
    return b;
}


dabei muss man darauf achten auf welchen weg man das ganze upcastet,
denn exsistieren zwei objekte in CButton (CRefcounting und Button, wenn
auch nur mit vtbl). daher caste ich immer über das interface.

das klappt auch, ich kann dutzende methoden der engine verwenden,
die alle auf dem selben, mittlerweile "erprobten" prinzip basieren.

nur dieses eine mal schmiert es ab.

das "b" ist ein CButton, der als Button-interface vorliegt.

mittlerweile versuche ich es mit dem guten alten const char *
Mfg Goti
www.gotbread.bplaced.net
viele tolle spiele kostenlos, viele hardware-basteleien :)

"Es ist nicht undicht, es läuft über" - Homer Simpson

dot

Supermoderator

Beiträge: 9 757

Wohnort: Graz

  • Private Nachricht senden

7

30.06.2009, 18:09

Zitat von »"Gotbread"«

anstatt die implementierten methoden der crefcounting-klasse
zu erben, hat er sich für die abstrakten methoden aus dem interface
entscheiden. das gab natürlich linkerfehler. das zieht sich durch
alles klassen durch.


zeig doch mal etwas code wo diese linkerfehler herkommen. das klingt auf jeden fall als ob da irgendwo was falsch is ;)

Gotbread

Alter Hase

  • »Gotbread« ist der Autor dieses Themas

Beiträge: 421

Beruf: Student (Etechnik) + Hiwi

  • Private Nachricht senden

8

30.06.2009, 19:13

aufs minimalste gekürtzt

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// testproj.cpp : Defines the entry point for the console application.

//

#include <iostream>

struct Refcounting
{
    virtual ~Refcounting(){}
    virtual void Release() = 0;
};
struct Element : virtual public Refcounting
{
    virtual void SayHello() = 0;
};
struct Dings : public Element
{
    virtual void MachNix() = 0;
};
////////////////////////////////////////

class CRefcounting : public virtual Refcounting
{
public:
    void Release()
    {
        delete this; // böse, aber nützlich

    }
};
class CElement : public Element, public CRefcounting
{
public:
    void SayHello()
    {
        std::cout << "hello" << std::endl;
    }
};
class CDings : public CElement, public Dings
{
};
Element *Create()
{
    //return static_cast<Dings *>(new CDings);

    return new CElement;
}
int main()
{
    Element *d = Create();
    d->SayHello();
    d->Release();

    std::cin.clear();
    std::cin.ignore(std::cin.rdbuf()->in_avail());
    std::cin.get();
}


wenn ich das so übersetze, geht alles glatt.

durch die abhängigkeit sowohl von den interfaces als auch von
den anderen implementationsklassen entsteht die mehrfachvererbung.

(das ist der einzige grund warum ich MI verwende, ich möchte einmal
den code in der implementationsklasse haben und nicht für jede
klasse alles neu schreiben müssen, auch keinen einzeilen-wrapper)

soweit so gut.

wenn ich von der untersten klasse nochmal was erben lassen will,
gibt es:

Quellcode

1
2
3
4
5
6
7
8
9
10
11
12
1>Compiling...
1>testproj.cpp
1>testproj.cpp(34) : warning C4250: 'CElement' : inherits 'CRefcounting::CRefcounting::Release' via dominance
1>        testproj.cpp(22) : see declaration of 'CRefcounting::Release'
1>testproj.cpp(37) : warning C4250: 'CDings' : inherits 'CRefcounting::CRefcounting::Release' via dominance
1>        testproj.cpp(22) : see declaration of 'CRefcounting::Release'
1>testproj.cpp(40) : error C2259: 'CDings' : cannot instantiate abstract class
1>        due to following members:
1>        'void Dings::MachNix(void)' : is abstract
1>        testproj.cpp(16) : see declaration of 'Dings::MachNix'
1>        'void Element::SayHello(void)' : is abstract
1>        testproj.cpp(12) : see declaration of 'Element::SayHello'


auch ein "virtual" hilft da nichts, egal wo
Mfg Goti
www.gotbread.bplaced.net
viele tolle spiele kostenlos, viele hardware-basteleien :)

"Es ist nicht undicht, es läuft über" - Homer Simpson

dot

Supermoderator

Beiträge: 9 757

Wohnort: Graz

  • Private Nachricht senden

9

30.06.2009, 19:36

du musst erstens mal MachNix() in CDing implementieren. CDings erbt von CElement und Ding welche beide von Element erben. Ergo musst du Element in den beiden zu einer virtuellen Basisklasse machen ;)
so kompilierts bei mir:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// testproj.cpp : Defines the entry point for the console application. 

// 

#include <iostream> 

struct Refcounting 
{ 
    virtual ~Refcounting(){} 
    virtual void Release() = 0; 
}; 
struct Element : virtual public Refcounting 
{ 
    virtual void SayHello() = 0; 
}; 
struct Dings : public virtual Element 
{ 
    virtual void MachNix() = 0; 
}; 
//////////////////////////////////////// 

class CRefcounting : public virtual Refcounting 
{ 
public: 
    void Release() 
    { 
        delete this; // böse, aber nützlich 

    } 
}; 
class CElement : public virtual Element, public CRefcounting 
{ 
public: 
    void SayHello() 
    { 
        std::cout << "hello" << std::endl; 
    } 
}; 
class CDings : public CElement, public Dings 
{ 
  void MachNix() {} 
}; 
Element *Create() 
{ 
    //return static_cast<Dings *>(new CDings); 

    return new CDings; 
} 
int main() 
{ 
    Element *d = Create(); 
    d->SayHello(); 
    d->Release(); 

    std::cin.clear(); 
    std::cin.ignore(std::cin.rdbuf()->in_avail()); 
    std::cin.get(); 
} 

Gotbread

Alter Hase

  • »Gotbread« ist der Autor dieses Themas

Beiträge: 421

Beruf: Student (Etechnik) + Hiwi

  • Private Nachricht senden

10

30.06.2009, 19:47

ich glaubs nicht, es kompiliert :D :D :D

das muss ich mal in die engine einbauen und testen.

aufjedenfall vielen dank!!!

(das wäre ja klasse wenn das funzen würde :) )

EDIT:

spricht etwas dagegen alle interfaces, die in der hierachie drinstecken
virtuell vererben zu lassen?
Mfg Goti
www.gotbread.bplaced.net
viele tolle spiele kostenlos, viele hardware-basteleien :)

"Es ist nicht undicht, es läuft über" - Homer Simpson

Werbeanzeige