Monthly archive for June, 2009

Podłączanie debugera VS do procesu

2009-06-27 17:48

Kiedy piszemy aplikację będącą już w na tyle zaawansowanym stadium, że nie objawia ona błędów przy pierwszym lepszym uruchomieniu, to zdarza się, iż uruchamiamy ją bez wsparcia debugera (co można zrobić standardowym skrótem klawiszowym Ctrl+F5 w Visual Studio). Mimo tego zawsze może się jednak zdarzyć jakiś nieprzewidziany wyjątek, błąd czy inna nieprawidłowość. Ba, może się tak zdarzyć w programie, który już dawno uznaliśmy za skończony!
Co wtedy? Przecież warunki powstania błędu mogą być trudne i pracochłonne do odtworzenia, jeślibyśmy uruchomili program ponownie – już w trybie debugowania. Nierzadko zresztą po takiej próbie błąd nagle w “magiczny” sposób zniknie, bo okaże się, że albo coś przeoczyliśmy, albo dany bug zależy od jakichś nieznanych jeszcze okoliczności, albo że całkiem niedeterministyczny (heisenbug).

Dlatego też lepiej posłużyć się już tą instancją programu, w której problem wystąpił, i przy jej pomocy poszukać błędu. W tym celu można użyć przydatnej opcji Visual Studio, pozwalającej na przyłączenie debugera do działającego procesu i dostępnej poprzez menu Debug > Attach to Process. Tam możemy wybrać po prostu naszą aplikację z listy działających procesów.
Jakie możliwości nam to daje? To zależy od tego, czy w pliku wykonywalnym docelowej aplikacji znajdują się odpowiednie symbole debugowe . W najlepszym wypadku będziemy mogli śledzić kod programu tak samo, jak przy uruchamianiu go z asystą debugera od samego początku (o ile, rzecz jasna, wcześniej otworzymy projekt, z którego kompilowaliśmy nasz program). Jeśli zaś nie dysponujemy źródłem aplikacji lub docelowy plik .exe nie posiada żadnych symboli debugowych, to oczywiście nadal możemy wykonywać działania typowe dla każdego debugera – jak krokowe wykonywanie instrukcji maszynowych czy ustawianie breakpointów na konkretnych adresach w pamięci.

Tags: , ,
Author: Xion, posted under Applications, Programming » 8 comments

O klasie Convert

2009-06-17 18:49

Stawiając pierwsze kroki w programowaniu w C#/.NET, można odkryć kilka ciekawych właściwości, które nie zawsze występują w innych językach. Jednym z nich jest całkiem dobre rozwiązanie odwiecznego problemu w kodowaniu, czyli zamiany między różnymi typami danych: zwłaszcza do i z łańcucha znaków.
Przykładem jest chociażby metoda ToString, która zrobi nam napis z dowolnego obiektu. Są też metody w stylu int.Parse, które potrafią odczytać liczbę zapisaną jako tekst i w zgrabny sposób rozwiązują jeden z najpowszechniejszych kłopotów wszystkich początkujących programistów :) (O ile oczywiście pomyślą oni o tym, że typ bądź co bądź, podstawowy, może mieć metody tak, jak klasa). W końcu, jest też rzutowanie; może ono służyć do “przywrócenia” właściwego typu obiektu, o którym z jakichś powodów “zapomnieliśmy”, ale również do konwersji między typami podstawowymi.

W sumie więc mechanizmów tego rodzaju jest dość dużo. Co więcej, nietrudno też – zwłaszcza w rzeczywistych, a nie przykładowych kodach – natrafić na jeszcze jeden. Jest to mianowicie użycie statycznej klasy Convert, zawierającej kilka metod typu ToDouble lub ToBoolean. I wydaje się, że korzystanie właśnie z niej najbardziej się opłaca. Dlaczego?
Ano głównie ze względu na jej uniwersalność – można z niej korzystać właściwie do wszystkich konwersji wyliczonych wyżej, i nie tylko. Daje to kod czytelniejszy, w którym od razu widać, co chcieliśmy zrobić – zwłaszcza jeśli alternatywą jest pokrętne obejście w rodzaju pośredniej konwersji do stringa. Ponadto klasa ta bywa sprytniejsza niż podane wcześniej sposoby, radząc sobie chociażby z typami danych używanymi przez API dostępu do baz danych (np. SqlInt32) i przerabiając je na bardziej użyteczne wartości.

W końcu, nie da się też ukryć, że korzystanie z mniej znanej, ale za to jakże wyspecjalizowanej klasy do prostych czynności jest niewątpliwą oznaką dużego profesjonalizmu… czyż nie? ;] I to pewnie jest najważniejszy argument ‘za’ ;P

Tags:
Author: Xion, posted under Programming » 17 comments

Boost i liczby losowe

2009-06-09 17:31

Narzekanie na jakość generatorów liczb (pseudo)losowych wbudowanych w języki programowania (takich jak rand() w C/C++) to dość popularne zajęcie wśród programistów gier. Chociaż nierzadko jest ono raczej bezpodstawne (albo ma charakter martwienia się na zapas), to niekiedy faktycznie losowość (czy raczej: nieregularność) uzyskiwanych wyników pozostawia wiele do życzenia.
Oczywiście zagadnienie generowania liczb “losowych” doczekało się mnóstwa opracowań jeszcze na długo przed tym, zanim stały się one potrzebne do wyliczania ruchu piłeczki odbitej od paletki w Pongu :) W praktyce możemy więc znaleźć mnóstwo dobrze działających implementacji różnego rodzaju generatorów liczb pseudolosowych dla większości znanych we Wszechświecie języków programowania.

Wspominam o tym, bo ostatnio sam potrzebowałem czegoś podobnego w C++. A gdzie zagląda każdy programista tego języka, gdy czegoś mu brakuje?… Do Boosta naturalnie :) Jako że jest tam (prawie) wszystko, niespecjalnie zdziwiło mnie, że istnieje cała biblioteka od szeroko pojętej losowości – Boost.Random. Zawiera ona rzecz jasna implementacje kilku różnych generatorów liczb pseudolosowych, różniących się nieregularnością wyników, szybkością, wymaganiami pamięciowymi, itd.
Tak naprawdę jednak tym, co czyni tę bibliotekę interesującą, jest sposób łączenia tych generatorów z wieloma dostępnymi tam rozkładami liczb losowych. Jeśli ktoś przysypiał na lekcjach matematyki lub wykładach z rachunku prawdopodobieństwa, to przypomnę, że – najprościej mówiąc – rozkład określa nam, jakie wartości mogą być wylosowane i jakie jest prawdopodobieństwo uzyskania każdej z nich. Przykładowo, standardowy rand teoretycznie oferuje nam liczby losowe o rozkładzie jednorodnym na zbiorze { 0, 1, …, RAND_MAX-1 }. Oznacza to, że – gdyby losowość była tu rzeczywista, a nie udawana – każda z tych wartości ma taką samą szansę na bycie wylosowaną. W przypadków innych rodzajów rozkładów nie musi tak być i niektóre wyniki mogą być bardziej preferowane niż inne (niezależnie od jakości generatora).

Jak więc wygląda to w Boost.Random? Ano całkiem zgrabnie. Najpierw bowiem wybieramy używany generator liczb, a potem pożądany rozkład, i obie te rzeczy możemy połączyć w jeden poręczny obiekt. Oto przykład:
#include

// generator liczb losowych
// (jest to pewna wersja znanego algorytmu Mersenne Twister)
boost::mt19937 rng;

// docelowy rozkład otrzymywanych liczb
// (jednorodny rzeczywisty na przedziale -1…1)
boost::uniform_real dist(-1, 1);

// wynikowy obiekt
boost::variate_generator >
random(rng, dist);

// kilka losowań
for (int i = 0; i < 10; ++i) std::cout << random() << std::endl;[/cpp] Jak nietrudno zauważyć, ten przykład pokazuje, jak bardzo C++ potrzebuje obecnego w C# od wersji 3.0 słowa kluczowego var ;-) Poza tym jednak widzimy, że otrzymany obiekt jest w użyciu bardzo prosty: aby dostać następną losową liczbę, wystarczy po prostu użyć na nim operatora (). Rezultat będzie od razu z właściwego przedziału (nie trzeba żadnych mnożeń czy dzieleń modulo), który określamy, definiując rozkład. Tutaj jest on jednorodny, ale oczywiście w Boost.Random mamy też mnóstwo innych, jak choćby geometryczny, wykładniczy, Gaussa (normalny), Bernoulliego, itp.
Nie to żeby były one jakoś często potrzebne, ale kiedy trafi się – tak jak mi – konieczność użycia któregoś z nich, to dobrze jest mieć tę bibliotekę pod ręką :)

Tags: ,
Author: Xion, posted under Programming » 10 comments

Płaska wizualizacja funkcji 3D

2009-06-02 18:43

Funkcje, których dziedziną jest podzbiór R2 (czyli płaszczyzna) ciężko jest przedstawić na płaskim ekranie w sposób poglądowy, a jednocześnie pozwalający na odczytanie jakichś wartości. O ile bowiem izometryczny rzut 3D wygląda efektownie, to poza ogólnym kształtem powierzchni nie przedstawia wielkiej ilości informacji. Sprawa wygląda trochę lepiej, jeśli taką figurę możemy jeszcze obracać… co aczkolwiek może być z kolei trudne do zrealizowania np. na wydruku :]

Jednym z wyjść może być tutaj zastosowanie sposobu używanego do map wysokości w grach. Nietrudno zauważyć, że są one niczym innym jak właśnie funkcjami typu R2->R, a reprezentowane są najczęściej jako bitmapy w odcieniach szarości: jasność piksela x,y odpowiada w nich wysokości odpowiedniego punktu terenu.
Takie heightmapy wprawdzie łatwo się rysuje, ale niespecjalnie nadają się do pokazania jako ilustracja czegokolwiek – odcienie szarości są przecież z definicji szare i nieciekawe :) Dlatego bardzo spodobał mi się prosty pomysł na uczynienie tej reprezentacji znacznie ładniejszą, o którym to usłyszałem niedawno.

Idea jest prosta: po co używać samych szarości, skoro mamy do dyspozycji całą paletę kolorów?… Każdy kolor w reprezentacji RGB jest przecież tożsamy z liczbą, co widać najbardziej, jeśli weźmiemy jego typową reprezentację 32-bitową:

W niej “największym” kolorem jest kolor biały (0x00FFFFFF, czyli 224 – 1), zaś “najmniejszym” czarny (0x0), a pomiędzy nimi włącznie występuje każdy z 2563 możliwych kolorów. Jeśli teraz przeskalujemy wartości naszej funkcji do tak określonego przedziału (lub nieco mniejszego), a następnie przekonwertujemy każdą z tych 32-(a właściwie 24-)bitowych liczb na odpowiadający jej kolor RGB, to otrzymamy w ten sposób heightmapę o znacznie bogatszej palecie barw niż poprzednio.
Jeśli ponadto używamy dokładnie takiej reprezentacji liczbowej kolorów, jak powyżej (najmłodsze bity na kolor niebieskie, a najstarsze używane na czerwony), to zauważymy, że rozkład barw w wyniku jest całkiem znajomy. I tak obszary niebieskie odpowiadają wartościom najmniejszym, zielone średnim, żółte – wysokim, a czerwone – najwyższym. Łącznie przypomina mapę hipsometryczną – czyli, jakby nie patrzył, mapę wysokości właśnie :)

Do czego może przydać się takie cudo, oprócz wspomnianej już wizualizacji funkcji R2->R na płaszczyźnie? Pewnie do niewielu rzeczy :) Ja wpadłem jednak na jeszcze jedno potencjalne zastosowanie.
Ponieważ wynikowy obrazek używa tak “geograficznych” barw, może on całkiem dobrze wyglądać jako… tło minimapy pokazywanej w grze, która rozgrywa się na terenie opisanym mapą wysokości. Oczywiście rzecz wymagałaby dopracowania: przede wszystkim bardziej starannego dobrania kolorów brzegowych (niekoniecznie białego i czarnego) oraz być może kolorów pośrednich (np. kolor poziomu morza), a i pewnie zastosowania jakichś filtrów na wynikowym obrazku (np. wygładzania). Efekty mogłyby być jednak interesujące, co widać obok.

 


© 2023 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.