Archive for Programming

Moc breakpointów w VS

2008-11-21 20:28

Śledzenie wykonywania programu to świetna metoda na znajdowanie przyczyn wielu błędów. Ale żeby coś sensownie śledzić, to najpierw zwykle trzeba dojść do interesującego fragmentu kodu – to zaś umożliwiają punkty przerwania, czyli po polsku breakpointy ;)
W najbardziej podstawowej wersji działają one niezwykle prosto i zwyczajnie zatrzymują debuger na wskazanej instrukcji. W Visual Studio możemy jednak wykorzystać w sposób znacznie bardziej zaawansowany; wystarczy bowiem – po postawieniu breakpointa kliknąć weń prawym przyciskiem myszy i już ukazuje się nam wielce interesujące menu podręczne. Mamy tam kilka przydatnych opcji, do których należą między innymi:

  • Location – umożliwia precyzyjne określenie, na jakiej instrukcji stawiamy breakpoint. Jest to użyteczne, gdy mamy np. krótką jednolinijkową instrukcję if, a punkt przerwania chcemy ustawić nie na sprawdzaniu jej warunku, lecz w środku jej bloku.
  • Hit Count – wyświetla nam okienko, w którym widać, ile razy breakpoint został “trafiony” od początku sesji debuggera. Możemy też określić tam, aby zatrzymanie następowało tylko przy jakimś określonym przejściu przez breakpoint. Może to być przydatne, jeśli np. debugujemy pętle, o której wiemy, że zaczyna się psuć w okolicach 4867 iteracji – właśnie tyle wciśnięć F5 możemy w ten sposób oszczędzić :)
  • When Hit – ta opcja pozwala z kolei na wykonanie dodatkowych akcji w momencie trafienia breakpointa, co obejmuje nawet możliwość uruchomienia makra sterującego zachowaniem IDE (!). Prawdopodobnie bardziej przydatne jest jednak wypisywanie komunikatu w oknie Output debugera; opcja ta jest na tyle zaawansowana, że może właściwie eliminować użycie funkcji typu OutputDebugString czy nawet dedykowanych logerów.
  • Condition to z kolei bodaj najprzydatniejsza opcja ze wszystkich. Pozwala mianowicie na określenie dodatkowego warunku, który musi być spełniony, aby punkt przerwania zadziałał. Jest to nieocenione do znajdowania błędów w skomplikowanych pętlach warunkowych, że o funkcjach rekurencyjnych nie wspomnę.
  • Filter to z kolei zaawansowana opcja, umożliwiająca ograniczenie działania breakpointa tylko do wybranych procesów lub wątków (rozpoznawanych przez identyfikator lub nazwę). Jak nietrudno się domyślić, przydaje się w programach równoległych.
  • W końcu Disable Breakpoint pozwala czasowo wyłączyć breakpoint bez usuwania go, czyli utraty tych wszystkich ustawień, które pieczołowicie wpisywaliśmy przy pomocy powyższych opcji :)

I tyle właśnie potrafią breakpointy w VS. Całkiem sporo, jak na jedną niepozorną, czerwoną kropkę :]

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

C#: referencje i porównywanie

2008-11-17 14:54

W C# obiekty zwykle dostępne są poprzez referencje. Zatem zmienna typu T (jeśli jest on klasą) nie zawiera samego obiektu T, lecz tylko odwołanie do niego. Porównując dwie takie zmienne przy pomocy operatora == domyślnie sprawdzamy więc, czy pokazują one na ten sam obiekt. Podobnie jest też przy użyciu domyślnej wersji metody System.Object.Equals; robi ona dokładnie to samo, co wspomniany operator. Można mądrze powiedzieć, że oba mechanizmy sprawdzają relację identyczności obiektów.

Czasami jednak chodzi nam o coś innego: chcemy sprawdzić, czy dwa obiekty są równe, np. w sensie zawartości swoich pól. Taka równość może zachodzić także wtedy, gdy obiektyte zostały stworzone zupełnie niezależnie. Znów można mądrze stwierdzić, że chcąc dokonać takiego sprawdzenia, realizujemy dla obiektów semantykę wartości. Co wtedy zrobić?… Ano przeciążyć tudzież nadpisać wspomnianą metodę Equals i/lub operator ==.
I tu zaczynają się schody, bo wcale nie jest łatwo zrobić to poprawnie, a jeszcze trudniej jest zrobić to sensownie. Wszystko zależy od tego, czy nasza klasa ma realizować wyłącznie ową nieszczęsną semantykę wartości czy też czasami będziemy jednak sprawdzać, czy dwie referencje pokazują na ten sam obiekt (a nie na dwa równe obiekty).

  • Jeśli tak (tzn. tylko sprawdzanie równości obiektów), to powinniśmy przeciążyć zarówno operator ==, jak i metodę Equals – i to przeciążyć tak, aby działały tak samo. Wtedy zwykle zajmujemy się najpierw operatorem, a później wykorzystujemy go w metodzie:
    1. struct Foo
    2. {
    3.     private int field;
    4.  
    5.     public static bool operator == (Foo a, Foo b)    { a.field == b.field; }
    6.     public override bool Equals(Object obj)
    7.         { return obj is Foo && this == (Foo)obj; }
    8. }

    W takiej sytuacji prawdopodobnie powinniśmy też posłużyć się strukturą. Najpewniej chodzi nam bowiem o typ, który ma zachowywać się jak podstawowy: czyli coś w stylu wektora, macierzy, kwaternionu, itp.

  • W przeciwnym przypadku (czyli: czasem porównujemy wartości, a czasem referencje) klasa powinna pozostać klasą, lecz należy przedefiniować jej metodę Equals. To jej powinniśmy używać, do sprawdzenia relacji równości między obiektami. Kiedy zaś chcemy zwyczajnie porównać referencje, możemy wtedy uciec się do operatora ==.

Uff, całkiem to zawiłe, prawda? Niestety nie jest łatwo uchwycić różnicę między równością a identycznością – a w językach, które obiekty realizują przez referencję sytuacja komplikuje się jeszcze bardziej. Zaś już chyba całkiem rozmywa się wtedy, gdy niektóre typy traktujemy per wartość, a niektóre per referencja…
A tak przecież jest C#. I jednocześnie podobno to jeden z prostszych języków do nauki i użytkowania :D

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

Trzy kroki do użycia biblioteki

2008-11-10 20:03

Żeby w C++ użyć w naszym programie kodu pochodzącego ‘z zewnątrz’ – a więc jakiejś biblioteki – trzeba się trochę napocić. Musimy bowiem odpowiednio “nakarmić” informacjami zarówno kompilator, jak i linker, co czasami skutkuje tym, że konieczne są w sumie trzy kroki. Są to:

  1. Dodanie odpowiedniej biblioteki statycznej (.lib) do wejścia linkera. W Visual C++ jest to uzupełnienie pola Additional Dependencies w zakładce Linker > Input we właściwościach projektu (można to także osiągnąć dyrektywami #pragma bezpośrednio w kodzie).
  2. Dołączenie odpowiednich plików nagłówkowych do naszego kodu – oczywiście przy pomocy dyrektywy #include. To ukłon w stronę kompilatora, aby mógł on poprawnie zidentyfikować wszystkie nazwy (funkcje, klasy, itp.) pochodzące z biblioteki.
  3. Odpowiednie potraktowanie ewentualnych przestrzeni nazw używanych przez kod zewnętrzny, czyli pisanie ich przed każdą nazwą (np. std::list zamiast list) lub skorzystanie z dyrektyw using.

To w sumie całkiem sporo pracy, wynikającej z niezbyt zautomatyzowanego sposobu budowania programów. W innych językach (głównie w tych, których pojęcie ‘projektu’ jest częścią ich samych, a nie tylko IDE) drugi z tych kroków często nie występuje w ogóle, bo został całkowicie zintegrowany z trzecim. Zawsze jednak trzeba wyraźnie wskazać, gdzie znajduje się kod biblioteczny, który ma zostać dołączony do naszego skompilowanego programu – czyli wykonać krok pierwszy. Ta czynność, często zapominana (bo zwykle nie znajdująca odzwierciedlenia w samym kodzie) jest bowiem najważniejsza.

Tags: ,
Author: Xion, posted under Programming » 1 comment

Kolejność na liście inicjalizacynej

2008-11-07 10:40

Jak wiadomo, do konstruktorów w C++ możemy doczepić listy inicjalizacyjne, pozwalające nadawać początkowe wartości polom macierzystej klasy (i nie tylko zresztą). Związany jest z tym pewien pozornie zaskakujący fakt, dotyczący kolejności inicjalizacji tych pól:

  1. class Foo
  2. {
  3.     private: string a, b;
  4.     public: Foo() : b("b"), a("a") { }
  5. };

Są one zawsze ustawiane w porządku zgodnym z ich deklaracjami w klasie. Tak więc powyżej inicjalizowane będzie najpierw pole a, zaś potem b – mimo że w konstruktorze kolejność jest odwrotna. Oczywiście w takim prostym przypadku nie ma to znaczenia, ale jeśli między polami występują zależności, wtedy może to być przyczyną “dziwnych” błędów. Porządniejsze kompilatory ostrzegają na szczęście przed rozbieżnymi kolejnościami pól w klasie i na liście w konstruktorze.

Można naturalnie zapytać, dlaczego w ogóle jest to zorganizowane tak nieintuicyjnie. Pierwsza nasuwająca się odpowiedź – “bo to C++” – nie jest bynajmniej zadowalająca :) Tak naprawdę przyczyną jest to, iż konstruktorów może być naturalnie więcej niż jeden:

  1. public:
  2.     Foo() : b("b"), a("a") { }
  3.     Foo(const Foo& foo) : a(foo.a), b(foo.b) { }

i mogą mieć one różną kolejność pozycji na liście inicjalizacyjnej. Gdyby to ona liczyła się bardziej, wtedy różne obiekty tej samej klasy byłyby konstruowane na różne sposóby. Co gorsza, sposób tej konstrukcji (kolejność inicjalizacji pól) musiałby zostać gdzieś zapamiętany na czas życia obiektu, jako że jego pola powinny być później zniszczone w kolejności odwrotnej do konstrukcji – zgodnie z zasadami języka.
Krótko mówiąc, zastosowanie “intuicyjnego” podejścia skutkowałoby nie tylko znacznie większymi niejasnościami, ale i dodatkowymi kłopotami zarówno dla programisty, jak i kompilatora.

Tags: , ,
Author: Xion, posted under Programming » Comments Off on Kolejność na liście inicjalizacynej

Mniejszy edytor

2008-10-14 21:54

Do programowania używamy głównie pełnowymiarowych IDE. W repertuarze narzędzi koderskich dobrze jest jednak mieć także edytor plików tekstowych bez całej tej projektowo-kompilacyjnej otoczki. Przydaje się on w wielu różnych sytuacjach, począwszy chociażby od typowego przypadku, gdy chcemy podejrzeć zawartość jakiegoś pliku z kodem bez odpalania całego “ciężkiego” środowiska. Poza tym wcale nie tak rzadko zdarza się, że musimy skrobnąć coś w języku, którego niekoniecznie używamy intensywnie na co dzień (jak choćby którymś z języków skryptowych).

Minimum, jakie taki programistyczny edytor powinien spełniać, to oczywiście podświetlanie składni. Wypadałoby też, by zawierał on większość funkcjonalności, jaką w zakresie manipulowania kodem oferuje IDE, np. wyszukiwanie w oparciu o wyrażenia regularne czy wyświetlanie numerowania linii i białych znaków. Dobrze by też było, gdyby umożliwiał o edycję kilku plików naraz. Prawdopodobnie najważniejsze jest jednak to, aby takie edytor był lekki: uruchamiał się jak najszybciej i tak też działał, nie zajmując przy tym wielu zasobów systemowych.
Pewnie każdy ma swój ulubiony program tego rodzaju, ale mimo to nie omieszkam polecić aplikacji, którą sam używam w charakterze “mniejszego edytora”: Programmer’s Notepad. Umie ona wszystko to, o czym wspomniałem wyżej, i jeszcze wiele więcej. Wśród poważniejszych braków, jakie mogę jej zarzucić, przypominam sobie jedynie brak wbudowanych narzędzi do konwersji między różnymi 8-bitowymi standardami kodowania znaków (z przeróżnymi UTF-ami itp. nie ma bowiem problemu). Ponieważ jednak możliwe jest dodawanie też własnych narzędzi uruchamianych z wiersza poleceń, w razie potrzeby można sobie odpowiednią funkcjonalność dodać samodzielnie :)

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

Dwa wymiary na dwa sposoby

2008-10-12 11:33

W C++ dynamiczne tablice z więcej niż jednym wymiarem tworzy się zwykle w sposób dość złożony. Najpierw bowiem – w przypadku dwóch wymiarów – tworzymy tablicę wskaźników na jej wiersze, a następnie same wiersze:

  1. int** tab = new int* [width];
  2. for (int i = 0; i < width; ++i)    tab&#91;i] = new int [height];[/cpp]
  3. Gdyby można było zrobić to w jednym kroku przy pomocy instrukcji w rodzaju:
  4. &#91;cpp]int** tab = new int [width][height];[/cpp]
  5. byłoby z pewnością nieco prościej. Nie jest to jednak możliwe i istnieje ku temu dobry powód. Rezultat takiej alokacji byłby bowiem inny niż pętli powyżej.
  6.  
  7. W tym pierwszym przypadku zawartość tablicy jest bowiem umieszczona w kilku osobnych kawałkach pamięci, nie zaś w jednym dużym. Sprawia to jednak, że zwykły operator indeksowania <code>[]</code> daje się zastosować wobec wskaźnika na taką tablicę w dobrze znany sposób:
  8. [cpp]tab[1][1] = 10;

Skądinąd wiadomo bowiem, że zapis tab[i] jest tylko cukierkiem składniowym zastępującym *(tab + i). Dlatego też w wyniku pierwszej dereferencji (indeksowania) musimy otrzymać wskaźnik (na wiersz tablicy), aby ponowną operację przeprowadzić drugi raz i dostać się do pojedynczego elementu.

Kiedy zaś tablica 2D jest umieszczona w jednym kawałku pamięci, wtedy element tab[i][j] powinien zostać obliczony inaczej – np. jako *(tab + j * width + i), jeśli elementy są układane w pamięci wierszami. Kompilator musiałby więc skądś wiedzieć, jaka jest szerokość (width) tablicy, a ponadto nie rozpatrywać każdej pary nawiasów kwadratowych osobno, lecz traktować je jako łączną operację indeksowania. Zwłaszcza pierwszy wymóg nie wydaje się rozsądny.

Warto jednak – jeśli zależy nam efektywności – używać drugiego sposobu przechowywania tablic dwuwymiarowych:

  1. int* tab = new int [width * height];
  2. tab[1 * width + 1] = 10;    // odp. tab[1][1] = 10
  3. delete[] tab;

Dostęp do elementów jest wtedy szybszy, bo oszczędzamy sobie jednej dereferencji wskaźnika (która może być kosztowna, jeśli tablica jest porozrzucana po pamięci). Szczegóły indeksowania można zaś opakować w zgrabną klasę z przeciążonymi operatorami.
Albo po prostu użyć gotowego rozwiązania, np. klasy multi_array z Boosta :)

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

Małe algorytmy

2008-10-08 13:44

Informatyka zna świetne rozwiązania wielu złożonych problemów, takich jak sortowanie czy wyszukiwanie najkrótszej drogi. Użycie tych powszechnie znanych algorytmów – nawet tych najbardziej skomplikowanych – jest zwykle całkiem proste. Albo bowiem dysponujemy już gotową implementacją, albo bez większych problemów możemy takową samodzielnie popełnić – z ewentualnymi usprawnieniami własnymi.

Często jednak zdarza się, że trzeba wymyślić coś znacznie mniejszego, rozwiązującego mniej modelowy, ale za to bardziej praktyczny problem. Niepotrzebne jest wtedy arcydzieło algorytmiki, lecz coś, co po prostu będzie dobrze działać. Osobiście uważam, że opracowywanie właśnie takich małych rozwiązań jest jedną z najciekawszych rzeczy w programowaniu w ogóle.
Przykłady? Jest ich tak dużo i są tak odmienne od siebie, że chyba niemożliwe jest podanie choćby kilku odpowiednio reprezentatywnych. Może to być krótki kod parsujący jakiś prosty tekst i wyciągający z niego pewnie informacje. Może to być metoda na przeskalowanie obrazka z zachowaniem jego aspektu (ilorazu szerokości do wysokości). Może to być również kod pozycjonujący jakąś kontrolkę wewnątrz okna o zmiennym rozmiarze. Może też chodzić o wyznaczenie rezultatu pojedynczego ataku w turowej grze strategicznej czy RPG. A nawet o, jak to ktoś ładnie nazwał, “silnik do pauzy” w owej grze ;] I tak dalej…

Niby nic skomplikowanego, czasami wręcz oczywistego – a jednak przecież trzeba to w miarę potrzeb wymyślać i zakodowywać. Bo gotowych rozwiązań problemów tak specyficznych po prostu nie ma. A bez takich małych algorytmów nie działałby żaden program – także ten, który musi używać również i tych “dużych” rozwiązań.
Może więc właśnie tutaj tkwi istota programowania?… Kto wie :)

Tags:
Author: Xion, posted under Programming, Thoughts » 5 comments
 


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