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

TalvinEx

Frischling

  • »TalvinEx« ist der Autor dieses Themas

Beiträge: 28

Wohnort: NRW

  • Private Nachricht senden

1

28.06.2016, 18:18

Unerklärliche Zugriffsverletzung?! (C++, SDL)

Hallo liebe Spieleprogrammierer!

Habe kürzlich ein neues Projekt begonnen und bin grade dabei, erstmal die grundlegenden Klassen dafür fertig zu machen.
Diese habe ich bis jetzt alle so oder ähnlich bereits verwendet, weshalb sich mir auch der Grund für genannte Zugriffsverletzung völlig entschließt. :/

Ich habe eine Klasse CSprite, deren Instanzen jeweils eine Grafik vertreten sollen, welche folgendermaßen aussieht und eigentlich super funktioniert:

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
class CSprite
{
public:

    CSprite();
    ~CSprite();

    void Load(const string sFilename);
    void Load(const string sFilename, int NumFrames,
        int FrameWidth, int FrameHeight);
    void LoadNew();
    void SetColorKey(int R, int G, int B);
    void SetPos(float fXPos, float fYPos);
    void Render();
    void Render(float fFrameNumber);
    SDL_Rect GetRect() { return m_Rect; }

private:
    SDL_Renderer *m_pRenderer;      // Zeiger auf die Textur des Frameworks
    SDL_Surface *m_pImage;          // Das eigentliche Bild des Sprites
    SDL_Texture *m_pTexture;        // Textur für die Surface
    SDL_Rect m_Rect;                // Rect des Sprites
    SDL_Rect m_FrameRect;           // Ausschnitt für Animationsphase
    int m_NumFrames;                // Anzahl der Animationsphasen
    int m_FrameWidth;               // Breite einer Animationsphase
    int m_FrameHeight;              // Höhe einer Animationsphase
    int m_NumFramesX;               // Wie viele Anim-Phasen in X-Richtung?

};

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
54
55
56
57
58
59
60
61
// Konstruktor
//
// Aufgabe: Zeiger auf Renderer holen und Membervariablen initialisieren
//
CSprite::CSprite() : m_pImage(nullptr), m_pTexture(nullptr)// Zeiger erstmal auf NULL setzen
{
    cout << "Konstruktor (Sprite) wurde aufgerufen!" << endl;

    // Zeiger auf Renderer holen
    m_pRenderer = g_pFramework->GetRenderer();

} // Konstruktor


// Load
//
// Aufgabe: Einfaches, nicht animiertes Sprite laden
//
void CSprite::Load(const string sFilename)
{
    // Surface freigeben, falls es bereits genutzt wurde
    if (m_pImage != nullptr)
        SDL_FreeSurface(m_pImage);

    // Bilddatei laden
    m_pImage = IMG_Load(sFilename.c_str());

    // Prüfen, ob alles glatt ging
    if (m_pImage == nullptr)
    {
        [...]
    }

    // Rect initialisieren
    m_Rect.x = 0;
    m_Rect.y = 0;
    m_Rect.w = m_pImage->w;
    m_Rect.h = m_pImage->h;


} // Load


// Render
//
// Aufgabe: Sprite rendern (ohne Animation)
//
void CSprite::Render()
{
    // Ist Textur noch leer?
    if (m_pTexture == nullptr)
    {
        // Ja, also Surface in Textur speichern
        m_pTexture = SDL_CreateTextureFromSurface(m_pRenderer, m_pImage);
        cout << "Surface wurde in Textur gespeichert!" << endl;
    }

    // Textur rendern
    SDL_RenderCopy(m_pRenderer, m_pTexture, nullptr, &m_Rect);

} // Render



Diese Klasse Sprite wird in eine Klasse CButton eingebunden, welche zwei Sprites speichert, um bei MouseOver die Grafik wechseln zu können. Diese Klasse scheint irgendwie Probleme zu machen, denn wie man später im Code der Main sehen wird, sind Sprites zu rendern kein Problem. Das Problem kommt mit dieser Klasse:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class CButton
{

public:


    void Init(const string sFilenameActive, const string sFilenamePassive,
              int xPos, int yPos);
    void Render();
    const SDL_Rect &GetRect(){ return m_ButtonRect; }
    void SetActive(bool bActive){ m_bActive = bActive; }

private:

    CSprite m_SpriteButtonActive;   // Sprite für den "aktiven" Button
    CSprite m_SpriteButtonPassive;  // Sprite für den "passiven" Button
    SDL_Rect m_ButtonRect;          // Rect für den Button
    bool m_bActive;                 // Button aktiv oder passiv?

};

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
// Init
//
// Aufgabe: Button initialisieren
//
void CButton::Init(const string sFilenameActive, const string sFilenamePassive,
                 int xPos, int yPos)
{
    cout << "init" << endl;
    // Sprite für aktiven und passiven Zustand des Buttons laden
    m_SpriteButtonActive.Load(sFilenameActive);
    m_SpriteButtonPassive.Load(sFilenamePassive);

    cout << "loaded" << endl;

    // Positionen der Buttons setzen
    m_SpriteButtonActive.SetPos(xPos, yPos);
    m_SpriteButtonPassive.SetPos(xPos, yPos);

    // Rect initialisieren
    m_ButtonRect.x = xPos;
    m_ButtonRect.y = yPos;
    m_ButtonRect.w = m_SpriteButtonActive.GetRect().w;
    m_ButtonRect.h = m_SpriteButtonActive.GetRect().h;

    // erstmal auf passiv setzen
    m_bActive = false;

    // Pink zu Transparenz
    m_SpriteButtonActive.SetColorKey(255, 0, 255);
    m_SpriteButtonPassive.SetColorKey(255, 0, 255);

} // Init


// Render
//
// Aufgabe: Button rendern
//
void CButton::Render()
{
    if (m_bActive)
        m_SpriteButtonActive.Render();
    else
        m_SpriteButtonPassive.Render();

} // Render


Hier werden der Init-Funktion zwei Dateipfade übergeben, mit deren Hilfe die beiden Sprites durch die CSprite::Load() Funktion geladen werden können.
Ich füge noch die Main.cpp an um dann zur genaueren Beschreibung des Problems zu kommen:

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
//Nur das wichtige aus der Main:
//
CSprite Sprite_Background;
    Sprite_Background.Load("Data/Graphics/Malen_nach_Zahlen/BackgroundBMP.bmp");

    CSprite Sprite_Test;
    Sprite_Test.Load("Data/Graphics/Malen_nach_Zahlen/TestPNG.png");

    CSprite Sprite_Button;
    Sprite_Button.Load("Data/Graphics/Back_Button_Passive.png");
    Sprite_Button.SetPos(400, 400);

    CButton Button_Test;
    Button_Test.Init("Data/Graphics/Back_Button_Active.png", "Data/Graphics/Back_Button_Passive.png", 500, 400);
    Button_Test.SetActive(true);

    CMenu MainMenu;
    MainMenu.LoadButton("Play", Button_Test);

    while (1 == 1)
    {
        g_pFramework->Clear();
        Sprite_Background.Render();
        Sprite_Test.Render();
        Sprite_Button.Render();
        Button_Test.Render();
        g_pFramework->Flip();
    }


Die Main besteht zur Zeit nur zu experimentellen Zwecken sei dazu gesagt, also meckert bitte nicht, wie sie aussieht^^

Da die Sprites bis Zeile 26 (Button_Test.Render()) beim Debugger kein Problem machen, sondern erst genannter Button, schließe ich, dass es sich um einen Fehler in CButton handeln muss, aber ich habe wirklich keine Idee, welcher das sein könnte..
Der Debugger springt von Button_Test.Render() in die entspr. Funkiton "Render" aus CSprite und hält dort bei Zeile 54 an mit einer Zugriffsverletzung :/

Hoffe irgendwer blickt da durch und kann mir helfen :D

Danke,

Tim
"Gewinner haben keine Angst vor dem Verlieren, nur Verlierer"
(~ein weiser Mann)

TalvinEx

Frischling

  • »TalvinEx« ist der Autor dieses Themas

Beiträge: 28

Wohnort: NRW

  • Private Nachricht senden

2

28.06.2016, 18:25

Hey,

kleines Update:
scheinbar muss es doch an der Klasse CMenu liegen.
Kommentiere ich die Zeile 18 in der Main aus, dann funzt alles prima..
Es wird immer komischer :wacko:

Hier der wichtige Code der CMenu-Klasse:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class CMenu
{
public:

    CMenu();
    void LoadButton(const std::string sButtonName, CButton &Button);
    void Render();
    string ProcessEvents();

private:

    std::map<std::string, CButton> m_Button_Map;    // Map für die Buttons
    std::pair<std::string, CButton> m_Button_Pair;  // Pair für die Button_Map
    std::map<std::string, CButton>::iterator It;    // Iterator für die Map
    int m_MouseXPos;                                // X-Position der Maus
    int m_MouseYPos;                                // Y-Position der Maus
    SDL_Event m_Event;                              // Events sollen hier gespeichert werden bzw hierhin abgerufen bei Abfrage

};

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Konstruktor 
//
// Aufgabe: Mausposition erstmal auf 0 setzen
//
CMenu::CMenu() : m_MouseXPos(NULL), m_MouseYPos(NULL)
{}


// LoadButton
//
// Aufgabe: Übergebenen Button in die Liste der zu überprüfenden Buttons aufnehmen
//
void CMenu::LoadButton(const std::string sButtonName, CButton &Button)  // vllt. keine Übergabe per Referenz, da das Menü weiterbestehen soll?
{
    // Paar aus den Parametern machen und in die Map schieben
    m_Button_Pair = make_pair(sButtonName, Button);
    m_Button_Map.insert(m_Button_Pair);

} // LoadButton



BTW: Warum ist 'st4ndartmäßig' hier scheinbar gesperrt? (4 = a)
"Gewinner haben keine Angst vor dem Verlieren, nur Verlierer"
(~ein weiser Mann)

3

28.06.2016, 18:43

An der Stelle, an der ein Button in die Map gepackt wird, wird er kopiert. Da die Button-Klasse (und auch CSprite) keine Kopierkonstruktoren/Zuweisungsoperatoren definiert haben, macht der Kompiler das für dich. Am Ende hast du zwei Objekte, deren Zeiger beispielsweise auf das gleiche SDL_Surface etc zeigen. Wird ein Objekt zerstört, wird auch dessen Surface durch den Destruktor, welchen du nicht gezeigt hast, (vermutlich) zerstört. Das andere Objekt hat nun einen Zeiger auf das gelöschte Surface.
Darum: markiere am Besten Kopierkonstruktor/Zuweisungsoperator als gelöscht:

C-/C++-Quelltext

1
2
3
//...
CSprite(CSprite&) = delete; //der Kompiler generiert ab sofort keinen Kopierkonstruktor mehr selber, welcher einfach nur die Zeiger kopieren würde
CSprite &operator=(CSprite&) = delete; //dasselbe mit dem Zuweisungsoperator


Jetzt kannst du CSprites nur noch über den vom Kompiler generierte Movekonstruktor/Moveoperator 'moven'. Falls du noch nicht weißt, was das ist, informiere dich mal darüber, oder frag nochmal nach.

Und: http://www.duden.de/rechtschreibung/Stan…Richtmasz_Guete

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »Techel« (28.06.2016, 18:50)


TalvinEx

Frischling

  • »TalvinEx« ist der Autor dieses Themas

Beiträge: 28

Wohnort: NRW

  • Private Nachricht senden

4

28.06.2016, 18:50

Danke, du rettest mir den Tag! :thumbsup:

Also..
Lösung 1: Sprites als Zeiger in den Buttons speichern statt auf dem Stack?
Lösung 2: Kopierkonstruktor schreiben?
"Gewinner haben keine Angst vor dem Verlieren, nur Verlierer"
(~ein weiser Mann)

Volker_Neff

Treue Seele

Beiträge: 249

Wohnort: Hamburg

  • Private Nachricht senden

5

29.06.2016, 09:37

Wenn du die Kopierstrukturen löscht so wie nach dem Vorbild von Tegel dann sollte der Compiler den Move Operator nutze. Dabei solltest du jedoch beachten das das ursprüngliche Object nun nicht mehr vorhanden ist. Kannst du je gerne mal ein wenig im Netz zu lesen. Wenn du jedoch weiterhin auf beide Elemente zugreifen möchtest solltest du auf Pointer umsteigen, eventuell auf Smart-Pointer.

Edit:
Um schon beim aufrufen deiner Funktion keinen Verweis zu benutzen kannst du auch dort schon deinen Button kopiere.

C-/C++-Quelltext

1
void function (Bar&& val)


Viel Erfolg

Schorsch

Supermoderator

Beiträge: 5 145

Wohnort: Wickede

Beruf: Softwareentwickler

  • Private Nachricht senden

6

29.06.2016, 13:10

Wenn du jedoch weiterhin auf beide Elemente zugreifen möchtest solltest du auf Pointer umsteigen, eventuell auf Smart-Pointer.

Gefährlich. Wenn er den Button an einer anderen Stelle benutzen möchte sollte er erstmal gucken ihn als Referenz zu nutzen. Smartpointer sind eine tolle Sache um den Speicher selbstständig zu verwalten, da sollte man aber nur zu greifen wenn man auch wirklich ein Heap-Objekt benötigt. Der Smartpointer ist dann im Normalfall dafür da um das Objekt zu halten und zu verwalten, soll heißen dass der Smartpointer an der Stelle ist wo das Objekt besessen wird. Was man nun machen kann ist den direkten Zeiger nach außen raus zugeben um das Objekt so weiter zu geben. Dieser Zeiger sollte dann aber nicht gelöscht werden. Als Beispiel, du hast eine BaseEntity Klasse die alle Definitionen von Entities im allgemeinen bereit stellt. Davon abgeleitet ist eine Klasse Player und eine Klasse Enemie. Du willst einen std::vector auf die Basisklasse BaseEntity haben worin dann alle Entitäten gehalten werden. Hier kannst du einen std::vector vom Typ std::smart_ptr auf BaseEntity nutzen. Zeiger brauchst du hier damit Polymorphie funktioniert. Möchtest du jetzt eine Entität nach außen geben um an anderer Stelle damit zu arbeiten dann gibt du nicht den Smartpointer raus, sondern direkt den eigentlichen Zeiger. Ein Smartpointer lässt sich nicht kopieren sondern nur moven. Deshalb darfst du nicht direkt den Smartpointer raus geben. Nur mal allgemein als Ansatz. Aber dafür würde ich vorschlagen ließt du sich mal in unique_ptr und std::move ein. Dann wird sicher einiges klarer.
„Es ist doch so. Zwei und zwei macht irgendwas, und vier und vier macht irgendwas. Leider nicht dasselbe, dann wär's leicht.
Das ist aber auch schon höhere Mathematik.“

TalvinEx

Frischling

  • »TalvinEx« ist der Autor dieses Themas

Beiträge: 28

Wohnort: NRW

  • Private Nachricht senden

7

29.06.2016, 18:55

Danke noch mal für die Antworten, habe das Problem jetzt auf jeden Fall verstanden und hoffe mal, dass ich sowas nicht nochmal einbaue bzw. das Problem dann selbst erkenne.

Wie genau funktioniert das mit dem 'moven' oder kennt da jemand ne gute Website zu?
Würde das ganz gerne mal so ausprobieren, weil mit meinem eigenen Kopierkonstruktor bei Sprite gibts zwar keine Fehlermeldung mehr, aber der Button wird einfach nicht auf dem Bildschirm gezeigt, obwohl ich die Render-Methode aufrufe. :/

Dass ich die "alte" Instanz des übergebenen Buttons dann nicht mehr brauche ist Voraussetzung, wenn man das mit dem 'moven' machen möchte, wenn ich das richtig verstanden habe?
"Gewinner haben keine Angst vor dem Verlieren, nur Verlierer"
(~ein weiser Mann)

Schorsch

Supermoderator

Beiträge: 5 145

Wohnort: Wickede

Beruf: Softwareentwickler

  • Private Nachricht senden

8

30.06.2016, 12:32

Einfach mal bei Google gucken. Hier siehst du zum Beispiel einige Seiten die helfen könnten.
„Es ist doch so. Zwei und zwei macht irgendwas, und vier und vier macht irgendwas. Leider nicht dasselbe, dann wär's leicht.
Das ist aber auch schon höhere Mathematik.“

TalvinEx

Frischling

  • »TalvinEx« ist der Autor dieses Themas

Beiträge: 28

Wohnort: NRW

  • Private Nachricht senden

9

30.06.2016, 18:06

Danke, auf die Idee wär ich nie gekommen.
Wollte wissen, ob da jemand ne gute Website zu kennt, damit ich die direkt nehmen kann, hat sich jetzt aber eh erledigt.
"Gewinner haben keine Angst vor dem Verlieren, nur Verlierer"
(~ein weiser Mann)

Schorsch

Supermoderator

Beiträge: 5 145

Wohnort: Wickede

Beruf: Softwareentwickler

  • Private Nachricht senden

10

30.06.2016, 19:26

Den Sarkasmus habe ich wohl verstanden :P
So viel gibt es dazu erst mal gar nicht zu sagen. Du hast ja denke ich Informationen gefunden. Viel muss man da halt erst mal gar nicht zu wissen. Wichtiger ist es halt das ganze einfach mal selbst zu testen und ein wenig damit rum zu spielen. Und dann natürlich immer schön im eigenen Code verwenden. An sich ist es gut Kopier- und Movekonstruktoren einfach immer selbst zu definieren oder halt eben zu löschen. Dann vergisst du nichts und wunderst dich nicht über komisches Verhalten.
„Es ist doch so. Zwei und zwei macht irgendwas, und vier und vier macht irgendwas. Leider nicht dasselbe, dann wär's leicht.
Das ist aber auch schon höhere Mathematik.“

Werbeanzeige