Autor Wątek: DestroyRenderer problem z użyciem  (Przeczytany 1485 razy)

Offline Mentaris

  • Użytkownik

# Styczeń 08, 2018, 19:25:55
Witam, wie ktoś może co może powodować crash, podczas wywołania funkcji DestroyRenderer w sdl2?

Mam utworzone wskaźniki do renderera i nie mam pewności czy usuwają się przed wywołaniem funkcji powyżej, czy to może skutkować crushem/zacięciem się programu?

Offline Mr. Spam

  • Miłośnik przetworów mięsnych

Offline RootKiller

  • Użytkownik

# Styczeń 09, 2018, 17:24:41
Użyj debuggera ;). Ewentualnie pokaż kod.

Offline Mentaris

  • Użytkownik

# Styczeń 09, 2018, 20:41:00
No właśnie najśmieszniejsze jest to że w debug mode nic się nie dzieje, jak uruchamiam po prostu po przez kompilator tylko raz na rok wywali błąd że coś z pamięcią jest nie tak. Jednakże analizowałem ten kod wiele razy i wydaje mi się że tutaj zadziałała moja niewiedza, ponieważ nic takiego nie mogłem znaleźć. Wiem że w trybie debug zwiększa się pamięć tablic itp, przez co trudniej ją naruszyć. Tak czy inaczej tylko podczas uruchomienia z pliku wywala, praktycznie za każdym razem gdy dociera właśnie do tego momentu.

Jeśli chodzi o kod, nie ma problemu, nie wspominałem jednak o tym ponieważ, to nie jest jeden kawałeczek kodu, tylko parę plików. Wątpię że komukolwiek będzie się chciało zaglądać. : p
Jednakże, jeśli... to proszę bardzo https://github.com/Mentaris/Trimwind

Błąd z tego co udało mi się znaleźć, wywala w destruktorze w GameLoop, gdy wywołana zostanie  funkcja DestroyRenderer.

Offline RootKiller

  • Użytkownik

# Styczeń 10, 2018, 12:35:38
Jak Ci wywali możesz zawsze podczepić debugger w tym momencie. Polecam Ci zacząć inicjalizować wskaźniki i wszystkie zmienne które znajdują się w klasie teraz bardzo możliwe że w release będziesz kasować pamięć której nie alokowałeś.

Ah no i tak poza tym lepiej nie używaj #pragma region - generalnie lepiej i ładniej sobie podzielić kod na funkcje niż używać regionów, region nie jest zbytnio "portable" :-).
« Ostatnia zmiana: Styczeń 10, 2018, 12:37:09 wysłana przez RootKiller »

Offline Mentaris

  • Użytkownik

# Styczeń 10, 2018, 14:37:04
Dzięki za porady, postaram się to ogarnąć.

Tylko za bardzo nie wiem jak ugryźć kwestię debuggowania. Zabrzmi może głupio, ale nigdy jakoś specjalnie się tym nie interesowałem. Zawsze wystarczył mi cout. : p
Rozumiem że do tego jest jakiś stand alone, który uruchamiam i wybieram, który proces chcę monitorować, tak? Polecasz może jakiś? Chyba że w visualu są takie funkcje a ja nawet o nich nie wiem.

Offline Lerhes

  • Użytkownik

# Styczeń 11, 2018, 13:47:22
Cześć Mentaris,

Tak w Visualu jest dostępny debugger.
Jeżeli problem pojawia się w trybie "Release" to po prostu zrób debugowanie w trybie Release.
Ostrzeżenie: W trybie Debug jeden krok przenosi Cię najczęściej do kolejnej instrukcji. W trybie Release (z powodu optymalizacji) jeden krok może Cię przenieść parę linii do przodu, a czasami nawet do tyłu!
Spróbuj i daj znać czy udało Ci się rozwiązać problem.

Pozdrawiam,
Lerhes

Offline Mentaris

  • Użytkownik

# Styczeń 12, 2018, 16:39:06
Hej Larhes!

Ano, jest. Fajna sprawa, podpiąłem się pod gre, no i teraz już wiem. Najpierw był problem z uszkodzeniem sterty. Poduczyłem się o wskaźnikach i zmodyfikowałem wszystko po kolei. Okazało się że używałem delete na wskaźniku który nie stworzyłem operacją new. To był najwidoczniej błąd. Teraz mam błąd 0xC0000005: Access violation reading location 0x00000010 czyli gdzieś mam wyzerowany wskaźnik, na którym próbuje coś działać. Podobno trzeba znaleźć - po przez debbuger - gdzie zostaje wyzerowany. Tyle wyczytałem.

Także pozdrawiam i dzięki za pomoc : )

Jeśli chodzi o te inicjalizowanie wskaźników. To jakoś tak średnio wiem jak to zrobić. Jest funkcja która pobiera wszystkie wskaźniki, ona jest uruchamiana tuż po stworzeniu obiektu. To nie wystarczy? W tym czasie nic nie jest zapisywane do tych wskaźników. Żeby inicjalizować wskaźnik, trzeba mieć albo zmienną pod którą się podebnie albo tworzyć miejsce w pamięci za pomocą new. To znaczy by wszystkie wskaźniki tworzyć za pomocą new, przepisywać im wartość zerową, a potem w destruktorach usuwać je i nadawać im nullptr?
« Ostatnia zmiana: Styczeń 12, 2018, 16:41:08 wysłana przez Mentaris »

Offline Lerhes

  • Użytkownik

# Styczeń 20, 2018, 01:30:19
Cześć Mentaris,

"Okazało się że używałem delete na wskaźniku który nie stworzyłem operacją new."
Ok, to na pewno był błąd i dobrze, że to poprawiłeś. Jeżeli to na co pokazywał wskaźnik to obiekt utworzony na stosie - to zdecydowanie było to coś co trzeba naprawić.

"Poduczyłem się o wskaźnikach i zmodyfikowałem wszystko po kolei. "
Martwi mnie co to znaczy.. mam nadzieję, że nie przesadziłeś.

"Jeśli chodzi o te inicjalizowanie wskaźników. To jakoś tak średnio wiem jak to zrobić. Jest funkcja która pobiera wszystkie wskaźniki, ona jest uruchamiana tuż po stworzeniu obiektu. To nie wystarczy?"
Nie do końca rozumiem o czym tutaj piszesz. Zgadzam się z tym, że wskaźniki trzeba inicjalizować przed użyciem. O co chodzi z funkcją która pobiera wszystkie wskaźniki ? Wiem, że dałeś link do kodu ale mógłbyś skopiować dokładnie fragment który nie działa / którego nie rozumiesz ?


"Żeby inicjalizować wskaźnik, trzeba mieć albo zmienną pod którą się podebnie albo tworzyć miejsce w pamięci za pomocą new. "
Ok, to jest napisane plus / minus poprawnie.

"To znaczy by wszystkie wskaźniki tworzyć za pomocą new, przepisywać im wartość zerową, a potem w destruktorach usuwać je i nadawać im nullptr?"
Rozumiem, że mówimy o wskaźnikach które są "polami" klasy ? W tym przypadku tak, należy je utworzyć za pomocą new (najczęściej tego właśnie chcemy ale są też inne przypadki). Nie "przepisujemy" (albo raczej przypisujemy) im wartości zerowej tuż po utworzeniu operatorem new, bo wtedy nie będą pokazywały na nic sensownego i w przypadku gdy ich użyjesz wyskoczy błąd: "Access violation". Znowu najlepiej gdybyś podał przykład (nie cały kod) w którym zobrazujesz problem.
W destruktorze warto usunąć to na co pokazują wskaźniki (żeby nie było wycieku pamięci) i przypisać im nullptr - co będzie też oznaczać, że już na nic sensownego nie pokazują. (Ktoś może Ci zarzucić, że to nie ma sensu bo i tak skoro destruktor właśnie się wykonuje, to i tak obiekt przestaje istnieć - nie ma co "tracić czasu" na przypisywanie nullptr do wskaźnika - ale to taka uwaga na boku).

Napiszę inaczej, z perspektywy twojego projektu: porozmawiajmy o klasie "Game" w pliku "GameLoop.h".
Klasa Game ma pole: "target_texture" którego typ to: "wskaźnik do SDL_Texture".
Widzę, że w destruktorze: "Game::~Game()" usuwasz to na co pokazuje ten wskaźnik przy pomocy metody z SDL: "SDL_DestroyTexture(target_texture);". To znaczy, że było by dobrze ustawić ten wskaźnik na jakiś obiekt typu "SDL_Texture" w przeciwnym razie funkcja "SDL_DestroyTexture" może zadziałać niepoprawnie. Przypuszczam, że programiści SDL sprawdzili przed zniszczeniem tego na co pokazuje wskaźnik "target_texture" czy "target_texture" nie jest null ale niestety w twoim przypadku im to nie pomoże. Dlaczego? Zaraz o tym porozmawiamy. Jeszcze jedna uwaga: Było by dobrze trzymać się zasady: Gdy wskaźnik "pokazuje" na coś sensowego to jego wartość je inna niż null. Jeżeli nie pokazuje na żaden obiekt (lub pokazywał, ale ten obiekt usunęliśmy) to przypisujemy pod każdy wskaźnik nullptr. Gdybyś tej zasady się trzymał i obawiał się że programiści SDL nie są ostrożni, mógłbyś napisać taki kod:
if(target_texture) { SDL_DestroyTexture(target_texture); target_texture = nullptr;}
Wtedy sam sprawdzasz czy na pewno wskaźnik pokazuje na coś sensownego zanim każesz zadziałać funkcji SDL_DestroyTexture.

Dlaczego piszę "Było by dobrze" oraz "Gdybyś"? Bo ty tej zasady nie przestrzegasz. Aby to poprawić powinieneś w konstruktorze Game::Game wpisać nullptr do target_texture o tak: target_texture = nullptr; I tak dla każdego innego wskaźnika! W przeciwnym razie,  target_texture będzie pokazywał na coś losowego.
Jeżeli chcesz powiedzieć, że i tak ustawiasz ten wskaźnik w funkcji: "int Game::startLoop(SDL_Window* window)" - ok zgadzam się. O ile funkcja ta zostanie wywołana jako pierwsza przed funkcją "void Game::render()" gdzie używasz target_texture. W twojej aplikacji tak właśnie jest - ale zasady to zasady.

Przestrzegając powyższej zasady możesz uniknąć innych pułapek. Co by było gdyby ktoś wywołał "startLoop" przez przypadek dwa razy? W twojej obecnej implementacji na razie nic bardzo złego - wyciek pamięci. Ale można by temu zapobiec:
if (target_texture )  { WypiszBlad("Ponownie tworzysz teksture, to jest jakis blad!"); } else { RenderTarget = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE); }

Jeżeli chcesz napisać: "Ale ja tak nie zrobię! Jestem ostrożny!" To się możesz bardzo szybko przekonać, że takie rzeczy się każdemu zdarzają - pytanie czy potrafimy to wykryć i szybko naprawić czy szukamy błędu przez tydzień.

Poza tymi ogólnymi uwagami, używasz "target_texture" w miarę poprawnie. Pytanie czy wszystkie wskaźniki tak są używane? To praca dla Ciebie!

Jeżeli wiesz dokładnie z którym wskaźnikiem jest problem (w której klasie) to napisz i zobaczę jak jest używany / inicjalizowany.

Pozdrawiam,
Lerhes