Posts from 2010

Chilijskie łososie mają anemię

2010-02-21 15:00

Źródło: Glenn Oliver/Visuals Unlimited/Getty Images

Tak, to stuprocentowa prawda – większość tych ryb w hodowlach w Chile jest dotknięta tą chorobą. Efektem tego będzie na pewno wzrost cen łososia również w sklepach.
No dobrze, ale co z tego – a dokładniej, czemu o tym piszę tutaj (bo fakt, że lubię dania z tych ryb pewnie nie jest wystarczającym uzasadnieniem ;])?… Otóż tytuł tej notki to doskonały przykład informacji, która jest prawdziwa, dokładna, a jednocześnie całkiem nieprzydatna i wywołująca tylko zdziwienie u odbiorcy.

To zupełnie tak, jak z niektórymi… komunikatami o błędach, produkowanymi przez kompilator C++ pracujący w ramach Visual Studio. Z punktu widzenia analizy kodu przez tenże kompilator mają one sens, jednak dla czytającego je programisty często mówią tyle co nic.
Z czasem aczkolwiek można nabrać wprawy i zacząć domyślać się, co tak naprawdę kompilator “miał na myśli”, produkując tę czy inną wiadomość o błędzie. To jednak wciąż wybitnie niepraktyczne i dlatego postaram się dzisiaj pomóc w tej kwestii, wyjaśniając prawdziwe znaczenie niektórych komunikatów wypluwanych przez Visual C++:

  • error C2146: syntax error : missing ‘;’. Czasami powodem tego błędu jest faktycznie brak średnika (np. na końcu definicji klasy). Często jednak pojawia się w – na oko – poprawnych deklaracjach zmiennych. Wtedy przyczyną jest nieznajomość typu tej zmiennej, więc pośrednio jest to brak dołączonego nagłówka, literówka w deklaracji tego typu, itp.
  • error C2181: illegal else without matching if. Pozornie “luźny” else to często efekt postawienia o jednego średnika za dużo – mianowicie średnika po bloku if. Jeśli używamy makr do zastępowania powtarzających się fragmentów kodu, to może tu chodzić o niepoprawne zdefiniowanie takiego makra.
  • error C2301: left of ‘->foo’ must point to class/struct/union. Ten i podobne błędy: C2302, C2510 oznaczają tyle, że kompilator nie rozpoznaje nazwy stojącej przed operatorem wyłuskania ->, . (kropką) lub ::. Niekoniecznie musi to znaczyć, że została ona źle zadeklarowana (i nie jest klasą/strukturą/unią) – najczęściej po prostu nie została zadeklarowana w ogóle.
  • error C2360: initialization of ‘foo’ is skipped by ‘case’ label. Ten komunikat (oraz analogiczny C2361) jest spowodowany brakiem nawiasów klamrowych otaczających bardziej skomplikowane kawałki kodu zawarte w poszczególnych przypadkach instrukcji switch (takie, które zawierają deklaracje nowych zmiennych). Bardzo podobny błąd dotyczy też używania etykiet i instrukcji goto, które, jak wiadomo, są złem potwornym (w tym przypadku żartuję oczywiście).
  • error C2440: ‘conversion’ : cannot convert from ‘Foo’ to ‘Bar’. Jeśli któryś z występujących w komunikacie (lub podobnym C2446) typów nie jest typem wbudowanym, to najpewniej brak tutaj dołączenia jakiegoś nagłówka, który daną konwersję by definiował. Inna możliwa przyczyna to przypuszczenie, że kompilator będzie na tyle sprytny i zastosuje dwie konwersje przez jakiś typ pośredni; niestety tak nie jest – naraz może być stosowana tylko jedna niejawna konwersja zdefiniowana przez programistę.
  • error C2512: ‘Foo’ : no appropriate default constructor available. Błąd ten może naprawdę zbić z tropu, o ile nie wiemy dokładnie, co jest jego przyczyną. Żeby się objawił, nie potrzeba tworzyć żadnych obiektów – wystarczy odziedziczyć po klasie, która nie ma domyślnego konstruktora. W takiej sytuacji musimy w każdym konstruktorze klasy potomnej zapewnić wywołanie konstruktora klasy bazowej (na liście inicjalizacyjnej) – albo po prostu dodać ten brakujący konstruktor domyślny w klasie bazowej.
  • error C2621: member ‘Foo::Bar’ of union ‘Foo’ has copy constructor. Typowym scenariuszem objawienia się tego błędu jest próba stworzenia unii zawierającej pole typu (w)string. Nie jest to możliwe – ze względu na obecność konstruktora kopiującego – i choć teoretycznie możliwe jest obejście tego faktu, stosowanie go jest bardzo, bardzo złym pomysłem. Lepiej po prostu przymknąć oko na “marnotrawstwo pamięci” i zmienić unię w strukturę.

Nie jest oczywiście możliwe wyliczenie wszystkich okoliczności, w których może objawić się każdy możliwy błąd. Powyższa lista jest więc trochę subiektywna w tym sensie, że zawiera tylko te pozycje, które przytrafiły mi się osobiście podczas kodowania. Bardziej doświadczeni programiści pewnie mogliby ją znacznie poszerzyć.

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

using w C#

2010-02-15 17:01

W C++ nie ma mechanizmu typu garbage collector, więc jedyne automatyczne zwalnianie obiektów, jakie w tym języku występuje, dotyczy tych lokalnych – tworzonych na stosie. Dlatego wszelkiego typu pomocnicze obiekty (np. uchwyty do zewnętrznych zasobów, jak pliki) deklaruje się tu zwykle jako właśnie zmienne lokalne.
W innych językach z kolei – dokładniej: w tych, w których GC występuje – praktycznie wszystkie obiekty są tworzone na stercie i zarządzane przez odśmiecacz pamięci. Nie musimy więc martwić się o to, jak i kiedy zostaną one zwolnione.

Ta zaleta staje się jednak wadą w sytuacji, gdy chcielibyśmy jednak móc swoje obiekty niszczyć samodzielnie. Jeśli na przykład mamy do czynienia ze wspominanym już uchwytem do zewnętrznego zasobu, to pewnie życzylibyśmy sobie, by został on zamknięty jednak nieco wcześniej niż na końcu działania programu (w niezbyt dużych aplikacjach zazwyczaj dopiero wtedy włącza się garbage collector). Inaczej będziemy niepotrzebnie zjadać zasoby systemowe.
W C# najlepszym sposobem na ograniczenie czasu życia obiektu jest instrukcja using (w tym kontekście to słowo kluczowe nie znaczy wcale użycia przestrzeni nazw!). Podajemy jej po prostu obiekt, którego chcemy użyć wewnątrz bloku; w zamian mamy zapewnione, że związane z nim zasoby zostaną zwolnione po wyjściu z tego bloku. Prosty przykład wygląda choćby tak:

  1. // otwarcie pliku do zapisu i zapisanie tekstu
  2. using (TextWriter tw = new StreamWriter("file.txt"))
  3. {
  4.     tw.Write ("qwertyuiop");
  5. }
  6. // tutaj plik jest już zamknięty

Czemu jednak samodzielnie nie wywołać tego Close czy innej podobnej metody, która służy do zwolnienia zasobu?… Ano choćby dlatego, że istnieje coś takiego jak wyjątki. O ile powoływanie się na ten fakt w C++ bywa zwykle nadmiarem ostrożności, o tyle w .NET wyjątki latają praktycznie stale i mogą być rzucane przez właściwie każdą instrukcję. Nie można więc pomijać możliwości ich wystąpienia i liczyć na to, że mimo niedbałego kodowania wyciek zasobów jakimś cudem nigdy nam się nie trafi.
Może więc lepiej użyć zwykłego bloku tryfinally? Zasadniczo using jest mu równoważny, a ponadto ma jeszcze dodatkowe zalety: automatycznie sprawdza istnienie obiektu przez jego zwolnieniem i ogranicza zasięg zmiennej przechowującej do niego referencję (jeśli deklarujemy ją tak, jak powyżej). Ponadto pozwala też nie wnikać w to, jaką metodę – Close, Disconnect, Release, End, … – trzeba by wywołać na koniec w bloku finally. Jako że wymagane jest, by obiekt w using implementował interfejs IDisposable, będzie to zawsze metoda Dispose, która zawsze posprząta i pozamyka wszystko co trzeba.

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

O obiektowości

2010-02-12 16:58

Kiedy programiści się nudzą, to często spierają się o terminologię. Przedmiotem takich sporów są zwykle nieco mgliste określenia, które czasami – zwykle niesłusznie – używane są jako atrybuty oceniające. Przykład? Choćby “obiektowość” – w zależności od punktu widzenia stwierdzenie, że dany kod/biblioteka/moduł/itp. są bardziej lub mniej obiektowe może stanowić albo zaletę, albo wadę. Zależy to głównie od ‘poglądów’ danej osoby na temat użyteczności podejścia OO w programowaniu.

Co to jednak znaczy, że dany kod jest obiektowy?… Nie uważam wcale, że należy go w tym celu napisać go w języku obiektowym. Twierdzę na przykład, że Windows API jest całkiem dobrym przykładem biblioteki realizującej obiektowy paradygmat, mimo tego że została ona stworzona w jak najbardziej strukturalnym C. Praktyczna różnica między poniższymi wywołaniami:

  1. foo->DoSomething (...);
  2. DoSomething (foo, ...);

jest bowiem właściwie żadna. Dodajmy do tego fakt, że z punktu widzenia programisty-użytkownika w WinAPI występuje zarówno dziedziczenie (rodzajów uchwytów), jak i polimorfizm (funkcje niezależne od typu uchwytów, np. CloseHandle), a więc bardzo obiektowe feature‘y.
Jeśli komuś wydaje się to naciągane i twierdzi, że w ten sposób pod obiektowość można podciągnąć właściwie każdy kod, to już spieszę z przykładem ukazującym, że tak nie jest. Otóż większa część biblioteki OpenGL obiektowa na pewno nie jest, zaś w tych nielicznych miejscach gdzie OOP jest niemal konieczny (jak zarządzanie teksturami czy buforami wierzchołków) pojawia się nieco dziwna koncepcja ‘indeksów’ używanych do identyfikowania poszczególnych obiektów.

Dla niektórych (łącznie ze mną) taki interfejs nie jest szczytem marzeń, a preferowane jest wyraźnie obiektowe podejście DirectX. Absolutnie jednak nie zgadzam się z twierdzeniem, że DirectX jest lepszy, bo bardziej obiektowy – to trochę tak jak powiedzenie, że ten obrazek jest ładniejszy, bo bardziej zielony. W obu przypadkach jest to kwestia gustu i nie powinniśmy zakładać, że cechy pozytywne dla nas są tak samo dobrze odbierane przez innych.
A wyższość DirectX nad OpenGL da się przecież uargumentować na wiele innych, lepszych sposobów :)

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

Pr0centy

2010-02-04 18:30

Jako że akurat trwa dla mnie okres najbardziej lubiany przez każdego studenta – czyli sesja – niespecjalnie miałem ostatnio czas na napisanie czegoś interesującego. Wiem, że niektórzy prowadzą bloga w ten sposób, że co kilka tygodni zamieszczają notkę o tym, dlaczego tak długo nie pisali… ale jakoś ta formuła niespecjalnie do mnie przemawia ;-)
Dlatego dzisiaj z braku lepszych pomysłów pozwolę sobie na mały off-topic, który aczkolwiek nawiązuje nieco do kilku wcześniejszych wpisów o tym, jak niektóre proste koncepcje matematyczne bywają źle używane i rozumiane. Tym razem rzecz dotyczy pojęcia niby trywialnego – procentów.

Wszyscy oczywiście wiedzą, co to znaczy, że jedna wielkość stanowi określony procent drugiej – mam nadzieję, że w celu zdobycia tej wiedzy wciąż wystarczy skończyć jedynie podstawówkę. O ile więc zastosowanie procentów w celu wyrażenia relacji zwykle nie sprawia nikomu kłopotów, o tyle znacznie powszechniejsze ich użycie – do opisywania wzrostów i spadków, czyli zmian jakichś wielkości w czasie – już niezupełnie.
Co mam tutaj na myśli? Weźmy klasyczny przykład “handlowy”, w którym cenę jakiegoś towaru wpierw podniesiono, a następnie obniżono o 20 procent. Czy plus dwadzieścia i minus dwadzieścia to razem zero?… Naturalnie nie, bo wartość procentowa zawsze występuje w relacji do jakiejś całości – w tym przypadku do aktualnej ceny. Jeśli wynosiła ona na początku x, to po tych dwóch operacjach będzie równa:

(100 + 20)\% * (100 - 80)\% * x = 1,2 * 0,8 * x = 96\% * x

Spadnie więc o 4%. W uproszczeniu można zatem powiedzieć, że “procenty nie dodają się”, chociaż trzeba oczywiście pamiętać, co się za tym stwierdzeniem kryje.
Może to zaskakujące, ale sprawa pośrednio dotyczy też… gamedevu, a zwłaszcza gier strategicznych i RPG, gdzie często występują różne efekty procentowe (np. zwiększenie obrażeń jednostki o x%), z których kilka może być aktywnych naraz. Ich “składanie” niekiedy jest zaimplementowane dobrze (tak jak powyżej), a niekiedy źle, co często daje się łatwo zauważyć.

Druga kwestia to opisywanie zmian takich wielkości, które same są wyrażone w procentach, jak tempa wzrostu (np. gospodarczego) lub udziału czegoś w całości (np. programów napisanych w danym języku w stosunku do wszystkich). Trzeba pamiętać, że nawet w takich przypadkach procenty zawsze oznaczają relację całość-część, niezależnie od tego w jakich jednostkach owa całość jest wyrażana.
Oto przykład: w pewnym kraju wzrost PKB dwa lata temu wynosił 2%, a w poprzednim roku już 3%. O ile (procent) się zmienił?… Oczywiście o:

\displaystyle \frac{3 - 2}{2} * 100\% = 50\%.

Jak nietrudno bowiem zauważyć, wzrósł o połowę. Chcąc ten fakt wyrazić w wielkościach bezwzględnych powinniśmy natomiast powiedzieć, że różnica wynosiła jeden punkt procentowy. Stwierdzenie, że jest ona równa 1% znaczyłoby bowiem tyle, że chodzi nam o jeden procent od wartości ‘2’, co rzecz jasna nie jest prawdą.

Tags:
Author: Xion, posted under Math » 6 comments

Łączenie efektów graficznych

2010-01-27 9:29

Internet pełen jest opisów, tutoriali i przykładowych kodów pokazujących, jak implementować różne efekty graficzne. Zakodowanie ich pojedynczo zazwyczaj nie jest więc problemem, o ile mamy jako takie pojęcie o grafice czasu rzeczywistego, bibliotece DirectX/OpenGL i programowaniu w ogóle.
Znacznie większym problemem jest połączenie kilku(nastu/dziesięciu) efektów tak, by było one zaaplikowane w jednym momencie do tej samej sceny. Ze względu na to, że każdy pojedynczy efekt może wymagać kodu w bardzo różnych miejscach potoku graficznego (chociażby w samej aplikacji oraz w kodzie shaderów), zintegrowanie wszystkich tych fragmentów nie wydaje się sprawą prostą.

Ostatnio aczkolwiek zajmowałem się praktycznym rozwiązywaniem tych kwestii; było to łączenie różnych rodzajów oświetlenia z cieniami generowanymi techniką shadow depth mapping i efektami postprocessingu w rodzaju depth of field. Pozwolę więc sobie podzielić kilkoma uwagami na ten temat. To może jeszcze nie są rady, jak dobrze zaprojektować architekturę silnika 3D, ale mały framework pewnie można o nie oprzeć ;] A zatem:

  • Należy wydzielić kod zajmujący się rysowaniem samych obiektów na scenie, gdyż będzie on wywoływany wielokrotnie. Niektórym może wydawać się to oczywiste, ale w ilu przykładowych kodach wywołania DrawPrimitive czy DrawSubset są w tej samej funkcji co Begin/EndScene? W rzeczywistym kodzie zapewne tak nie będzie, bo dana scena będzie na pewno renderowana wielokrotnie.
  • Trzeba odpowiednio zająć się macierzami przekształceń. Ważne jest na przykład wydzielenie w shaderze macierzy lokalnego przekształcenia każdego obiektu. Nie można jej po prostu złączyć z macierzą WORLD (lub MODELVIEW w OpenGL), bo nasza scena będzie renderowana kilka razy w potencjalnie różnych widokach (kamery, światła, obiektu odbijającego otoczenie, itp.). Dodatkowo mogą być nam potrzebne punkty w różnych przestrzeniach, np. w układzie widoku obserwatora i widoku od konkretnego światła naraz. Wreszcie, nie należy zapominać o prawidłowym przekształcaniu wektorów normalnych. W sumie więc sekcja deklaracji pliku z shaderami może wyglądać np. tak:
    1. float4x4 ObjectTransform; // przekszt. lokalne obiektu
    2. float4x4 CameraWorld; // przekszt. globalne sceny
    3. float4x4 CameraWorldRotation; // jw. ale z samą rotacją
    4. float4x4 CameraView; // przekszt. do przestrzeni widoku
    5. float4x4 CameraProjection; // przekszt. do przestrzeni rzutowania
    6. float4x4 LightWorldViewProjection; // przekst. do przestrzeni światła
    7. // itd.

    Są tutaj jeszcze dwie sprawy warte zaznaczania. Po pierwsze, obiekty rysujące się na scenie muszą wiedzieć, gdzie ustawiać swoją macierz lokalnego przekształcenia. We wszystkich używanych shaderach nazwa odpowiedniej stałej (tutaj ObjectTransform) musi być taka sama; najlepiej też żeby mapowała się na te same rejestry stałych cn. Naturalnie kod renderujący obiekty musi też “wiedzieć”, żeby korzystać właśnie z niej zamiast z macierzy przekształceń z fixed pipeline – czyli np. wywoływać effect->SetMatrix("ObjectTransform", &mat); zamiast device->SetTransform (D3DTS_WORLD, &(currWorld * mat)); w przypadku DirectX).
    Po drugie, nie trzeba “dla efektywności” przekazywać do shadera iloczynów macierzy, jeśli używamy także ich poszczególnych czynników. Można bowiem zupełnie bezkarnie mnożyć je na początku kodu shadera:

    1. float4x4 CameraObjectWorld = mul(ObjectTransform, CameraWorld);
    2. float4x4 CameraWVP = mul(CameraObjectWorld, mul(CameraView, CameraProjection));
    3. // dalej reszta shadera
    4. Out.Position = mul(float4(In.Position, 1), CameraWVP);

    Kompilator wydzieli ten kod w postaci tzw. preshadera i zapewni, że będzie on wykonywany tylko raz (a nie dla każdego wierzchołka/piksela).

  • Konieczne jest zadbanie o dobrą obsługę render targetów. Powinna być ona przezroczysta dla poszczególnych efektów – nie muszą one wiedzieć, czy renderują bezpośrednio na ekran, czy do tekstury. Jednocześnie każdy efekt powinien móc określić, do którego RT chce aktualnie renderować i mieć potem możliwość wykorzystania wyników jako tekstur w kolejnych przebiegach. Generalnie do tych celów wystarcza prosty menedżer oparty np. na słowniku identyfikującym poszczególne RT za pomocą nazw: "ShadowMap", "DepthMap, "Scene" itp.
  • W bardziej skomplikowanych przypadkach trzeba pewnie będzie złączyć shadery. W chwili obecnej jest to pewnie jeden z najbardziej złożonych problemów przy tworzeniu silnika graficznego, ale istnieje szansa, że wprowadzane w DirectX 11 dynamiczne linkowanie shaderów będzie to w istotny sposób ułatwiało.
    Jeśli na razie nie chcemy się mierzyć z tym problemem, to można niekiedy go ominąć kosztem dodatkowych przebiegów renderowania. Przykładowo, cienie można nakładać na gotową scenę z już policzonym oświetleniem zamiast oświetlać i cieniować piksele w jednym passie.

Ogólnie trzeba przyznać, że implementowanie wielu efektów działających naraz w tej samej scenie to zagadnienie złożone i dość trudne. Chociaż więc starałem się podać kilka porad na ten temat, to w rzeczywistości niezbędne jest tutaj spore doświadczenie z różnymi rodzajami efektów, zarówno w teorii jak i praktyce.

Ostatnia wartość zwracana przez funkcję

2010-01-19 9:23

Debugując program w pracy krokowej w Visual Studio, możemy podglądać wartości wszystkich zmiennych, jakie są dostępne w zasięgu punktu wykonania. Jest to możliwe za pośrednictwem okienka tzw. czujek (watches). Automatycznie wypełnia się ono zmiennymi lokalnymi oraz ewentualnym wskaźnikiem this, pozwalającym podejrzeć wartości pól obiektu, jeśli znajdujemy się akurat w jego metodzie.
Jednak oprócz rzeczywistych zmiennych możemy śledzić też pewne specjalne “pseudozmienne”, udostępniane przez debuger VS. Mają one nazwy zaczynające się od znaku dolara $ i potrafią być bardzo przydatne, co pokażę na przykładzie.

Powiedzmy, że mamy kod składający się z wielu następujących po sobie wywołań funkcji bibliotecznych. Każda z nich zwraca rezultat liczbowy, informujący o (nie)powodzeniu operacji, którego jednak w kodzie nie sprawdzamy – pewnie dlatego, że nie chciało nam się pisać tych wszystkich ifów :) Taka sytuacja może wystąpić chociażby przy ciągu wywołaniach DirectX: ogromna większość funkcji z tej biblioteki zwraca wynik typu HRESULT, który jest liczba zawierającą kod błędu, jeśli takowy wystąpił.
Zauważamy teraz, że ów ciąg wywołań najpewniej zawiera jakiś błąd. Objawem w DX może być chociażby słynne “nic nie widać” :) Jak teraz dojść, które z wielu wywołań zwraca błąd, jeśli w żadnym z nich nie tylko nie sprawdzamy wyniku, ale wręcz w ogóle go nie zapisujemy?…

Podejrzenie wartości EAX w VS

Istnieje na szczęście proste i wygodne rozwiązanie, które nie wymaga żadnych zmian w kodzie. Otóż przy pomocy debugera VS i jego pseudozmiennych możemy podejrzeć wartość rejestrów procesora. Wystarczy użyć symbolu $nazwa_rejestru, jak np. $eax. I właśnie $eax jest tutaj świetnym przykładem, bo rozwiązuje nasz problem. Otóż przez rejestr EAX przekazywany jest zawsze 32-bitowy rezultat zwracany przez każdą porządną funkcję. Przechodząc więc krokowo po naszym kodzie możemy sprawdzić, jak zmienia się ten rejestr po każdym wywołaniu, podglądając w ten sposób wartość, jaką zwróciła w nim funkcja. Wystarczy więc tylko znaleźć to, w którym rezultat informuje o niepowodzeniu… i voila – mamy nasz błąd :)

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

Indeksowanie od tyłu

2010-01-12 20:58

Pewna programistyczna mądrość ludowa uczy, że jedynymi liczbami bezpośrednio występującymi w kodzie powinny być tylko 0 lub 1. Brak lub nadmiar tej drugiej jest przy tym często pojawiającym się błędem, który zyskał swoją własną nazwę pomyłek o jedynkę (off-by-one errors).

Okazji do popełnienia tego rodzaju gaf jest sporo, a objawiają się one przede wszystkim wtedy, gdy mamy do czynienia z tablicami, łańcuchami znaków, kolekcjami, itd. – innymi słowy wszystkim, co da się indeksować. Jako rzecze C++ i większość innych “normalnych” języków, indeksy tablic rozpoczynają się od zera. Startowanie ich od jedynki ma aczkolwiek pewną zaletę: pętle przeglądające zawartość tablic mają wtedy prawie identyczne postaci niezależnie od kierunku przeglądania:

  1. for (int i = 1; i <= n; ++i) // w przód
  2. for (int i = n; i >= 1; --i) // w tył

W przypadku indeksowania od zera pętla odliczająca wstecz jest wyraźnie różna:

  1. for (int i = 0; i < n; ++i) // w przód
  2. for (int i = n - 1; i >= 0; --i) // w tył

Pisząc ją, trzeba więc zwrócić baczniejszą uwagę na to, co się robi.

Jednak nawet wtedy można potknąć się o pewien kruczek, gdy pod n podstawimy rzeczywiście używane wartości. Niech to będzie np. wielkość kolekcji STL czy długość łańcucha znaków std::string – a więc coś w stylu x.size(). Otóż takie wyrażenie zwraca liczbę typu równoważnego size_t, który z kolei jest równy ni mniej, ni więcej, jak tylko unsigned int. Jest to więc liczba bez znaku.
W zwykłej wersji pętli (liczonej do przodu) powoduje to ostrzeżenie kompilatora o niezgodności typów ze znakiem i bez znaku (signed/unsigned mismatch) w warunku i < x.size(), gdy i jest typu int. Jednym ze sposobów na pozbycie się tego warninga jest oczywiście zamiana typu licznika na size_t. Jeśli teraz mamy wystarczająco zły dzień, to przez analogię będziemy licznikiem tego typu indeksować również pętlę odwrotną:

  1. for (size_t i = x.size() - 1; i >= 0; --i) // no i zonk

I nieszczęście gotowe. Zauważmy bowiem, że odliczanie do tyłu tablicy/kolekcji indeksowanej od zera wymaga, by na koniec licznik przyjął na koniec wartość ujemną; inaczej warunek i >= 0 nigdy nie stanie się fałszywy. To się jednak nigdy nie stanie, gdy i jest liczbą bez znaku; zamiast tego nastąpi przekręcenie na maksymalną wartość dodatnią. Skutkiem będzie pętla nieskończona lub (częściej) access violation.

Co z tego wynika? Ano to, żeby… indeksowania generalnie unikać :) Po to bowiem zarówno STL, jak i każda inna biblioteka pojemników w każdym sensownym języku ma inne sposoby dostępu do elementów – choćby iteratory – aby z nich korzystać. I unikać takich “ciekawych” błędów jak powyższy :)

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


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