Monthly archive for December, 2009

Triki z PowerShellem #13 – Prędkość czasu

2009-12-31 13:47

Ostatni dzień roku to dobry moment na różne dziwne przemyślenia na temat czasu. Kiedyś z tej okazji wymyśliłem zupełnie nowy system kalendarza, a dzisiaj przypomniałem sobie o niezupełnie poważnym pomyśle autorstwa Rega, ze zbliżonej dziedziny.
Chodzi o oryginalną wielkość “fizyczną”: prędkość czasu. Ma ona ilościowo mierzyć znany wszystkim fakt, że wrażenie upływającego czasu jest różne w różnych sytuacjach. Gdy w rzeczywistości upłynął pewien czas t, a nam wydawało się, że minął raczej czas t', to odczuwana przez nas prędkość czasu \tau wynosi

\displaystyle \tau = \frac{t}{t'}.

Dla przykładu: na nudnym wykładzie mogło minąć ledwie 15 minut, podczas gdy nam wydaje się, że siedzimy na nim już godzinę; wówczas \tau_1 = 0,25 / 1 = 0,25. Z kolei grając przez trzy godziny możemy mieć wrażenie, że upłynęło tylko 30 minut – wtedy \tau_2 = 3 / 0,5 = 6. Jak widać \tau_1 < \tau_2, zatem teoria zgadza się tutaj z intuicją, co pozwala wnioskować, że wielkość jest dobrze zdefiniowana :)

Pozostaje jeszcze kwestia jej pomiaru. W swoim oryginalnym artykule (który w tajemniczy sposób zniknął z sieci) Reg zaproponował, że można by go przeprowadzić za pomocą odpowiedniego programu. Miałby on działać w tle, losować pewien interwał czasu i po jego upływie pytać użytkownika, ile czasu według niego upłynęło od ostatniego pytania. Z dokładnością do małych błędów pomiarowych (np. czasu potrzebnego na wpisanie odpowiedzi) metoda wydaje się dobra – czemu więc by jej nie zaimplementować? :]
To właśnie uczyniłem, posługując się oczywiście swoim ulubionym narzędziem na takie okazje, czyli PowerShellem. Pewnym problemem było to, jak w skrypcie konsolowym postarać się o mierzenie czasu w sposób, który byłby niezauważalny dla użytkownika. Da się to jednak łatwo zrobić, posługując się… wbudowanym w system Harmonogramem zadań (Task Scheduler). Istnieje bowiem do niego dobrze udokumentowane, ładne i przejrzyste COM-owe API (dostępne od Visty wzwyż), którego użycie w PowerShellu wygląda zresztą kilka razy lepiej niż w C++ ;P
Wynikowy skrypt wygląda więc następująco:

  1. # timespeed.ps1
  2. # Skrypt do mierzenia prędkości czasu
  3. param ([int]$minutes = 0)
  4.  
  5. # Stałe
  6. $TASK_NAME = "TimeSpeed_Task"   # Nazwa zadania w Harmonogramie
  7.  
  8. # Pobieramy obiekt harmonogramu zadań
  9. $ts = New-Object -ComObject Schedule.Service
  10. $ts.Connect($null, $null, $null, $null)
  11. $tf = $ts.GetFolder($(New-Object String @([char]92, 1))) # "\"
  12.  
  13. if ($minutes -eq 0)
  14. {
  15.     # Pierwsze uruchomienie
  16.     $task = $ts.NewTask(0)
  17.     $task.Data = ""
  18.     $continue = "T"
  19. }
  20. else
  21. {
  22.     # Kolejne uruchomienia
  23.     $task = $tf.GetTask($TASK_NAME).Definition
  24.    
  25.     # Pytamy użytkownika i liczymy prędkość czasu
  26.     $userMinutes = Read-Host -Prompt "Liczba minut od ostatniego razu"
  27.     $d_tau = $minutes / $userMinutes
  28.     if (-not [String]::IsNullOrEmpty($task.Data))
  29.     {
  30.         $timeData = $task.Data.Split(',')
  31.         $totalTime = [int]$timeData[0]
  32.         $totalUserTime = [int]$timeData[1]
  33.     }
  34.     $totalTime += $minutes
  35.     $totalUserTime += $userMinutes
  36.     $tau = $totalTime / $totalUserTime
  37.     $task.Data = $totalTime.ToString() + "," + $totalUserTime.ToString()
  38.    
  39.     # Wyświetlamy aktualne wyniki
  40.     "Rzeczywista liczba minut: " + $minutes | Out-Host
  41.     "Chwilowa prędkość czasu: " + $d_tau | Out-Host
  42.     "Całkowita prędkość czasu: " + $tau | Out-Host
  43.    
  44.     # Pytamy użytkownika, czy chce kontynuować
  45.     $continue = Read-Host -Prompt "Wykonać następny pomiar (T/n)?"
  46. }
  47.  
  48. if ($continue -ine "n")
  49. {
  50.     # Losujemy nowy interwał czasowy (od 5 do 60 minut, co 5)
  51.     $rand = New-Object System.Random
  52.     $nextMinutes = ($rand.Next(0, 12) + 1) * 5
  53.    
  54.     # Dodajemy trigger dla zadania (czas: za X minut)
  55.     $task.Triggers.Clear()
  56.     $trigger = $task.Triggers.Create(1) # 1 - trigger na określony czas
  57.     $nextTime = [DateTime]::Now.AddMinutes($nextMinutes)
  58.     $trigger.StartBoundary = $nextTime.ToString("o")
  59.    
  60.     # Dodajemy akcję (uruchomienie skryptu)
  61.     $task.Actions.Clear()
  62.     $action = $task.Actions.Create(0)   # 0 - uruchomienie polecenia
  63.     $action.Path = "powershell"
  64.     $action.Arguments = "-Command . '" + $MyInvocation.MyCommand.Path
  65.     $action.Arguments += "' -minutes " + $nextMinutes.ToString()
  66.    
  67.     # Dodajemy zadanie
  68.     # (6 - stwórz lub uaktualnij)
  69.     # (3 - zadanie uruchamiane tylko przy zalogowanym użytkowniku)
  70.     $tf.RegisterTaskDefinition($TASK_NAME, $task, 6, $null, $null, 3) | Out-Null
  71. }
  72. else
  73. {
  74.     # Koniec - usuwamy zdarzenie
  75.     try { $tf.DeleteTask($TASK_NAME, 0) } catch { }
  76. }

Przy swoim pierwszym uruchomieniu tworzy on nowe zadanie w Harmonogramie, które polega na jego ponownym odpaleniu po upływie losowej liczby minut (5, 10, 15, itd. aż do maks. 60 minut). Wtedy użytkownik jest pytany o to, ile czasu według niego upłynęło, a następnie wyświetlają się częściowe wyniki pomiaru. Wówczas można zdecydować o jego kontynuowaniu lub zakończeniu. (Zawsze można też ręcznie usunąć zadanie z Harmonogramu, rzecz jasna).

Skrypt w PSh do mierzenia prędkości czasu

I to w sumie tyle. Można już przystąpić do badań określających dokładnie, jak bardzo dłuży nam się czas do północy :))

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

Odczytywanie tekstu z gwiazdek

2009-12-29 12:24

Pole tekstowe hasłaAplikacje wymagające podania od użytkownika jakichś haseł zwykle ukrywają ich znaki, zastępując je “gwiazdkami”, czyli np. znakami asterisk (*) lub dużymi kropkami. Dzięki temu lekko zwiększa się poziom bezpieczeństwa programu, gdyż osoba niepowołana nie może tak po prostu odczytać hasła, patrząc na ekran.
Niezbyt imponujące określenie ‘lekko’ jest tu jednak bardzo na miejscu, bo istnieje całe mnóstwo narzędzi, które potrafią takie “zagwiazdkowane” hasła bez trudu odczytać. W jaki sposób to robią?

Otóż pola tekstowe dla haseł często są po prostu zwykłymi editboksami. Różnicą jest to, że mają przy tym ustawioną specjalną flagę: styl ES_PASSWORD; sprawia ona, że zawartość pola jest wyświetlana w sposób zamaskowany. Zakładając, że znamy jego uchwyt (HWND) – pozyskany na przykład z pozycji myszy i funkcji WindowFromPoint – możemy ów styl po prostu wyłączyć:

  1. SetWindowLong (hEditBox, GWL_STYLE,
  2.     GetWindowLong(hEditBox, GWL_STYLE) & ~ES_PASSWORD);

a następnie ustawić jeszcze znak zastępczy (“gwiazdkę”) na \0:

  1. SendMessage (hEditBox, EM_SETPASSWORDCHAR, 0, 0);

Wtedy kontrolka powinna zacząć normalnie wyświetlać swoją zawartość bez zamaskowania, więc użytkownik będzie w stanie ją odczytać. Jeśli natomiast chcielibyśmy programowo się do niej dostać, trzeba wysłać do kontrolki komunikat WM_GETTEXT. (Funkcja GetWindowText nie zadziała, jeśli editbox jest w innym procesie – a w tym przypadku właśnie tak jest).

Czy można swój własny program zabezpieczyć przed takim “hackiem”? Oczywiście można – wystarczy nadpisać domyślny sposób obsługi wspomnianego wyżej komunikatu WM_GETTEXT, by nie zwracał on rzeczywistego tekstu zapisanego w polu tekstowym. Da się to zrobić przy pomocy techniki subclassingu, czyli podmiany procedury zdarzeniowej okna (tutaj: pola tekstowego). Trzeba tylko pamiętać, żeby zapewnić przy okazji jakiś sposób, żeby ten tekst jednak jakoś pobierać – np. poprzez zupełnie nową wiadomość:

  1. const DWORD WM_GETREALTEXT = WM_USER + 0x0177;

Najpewniejszą metodą jest aczkolwiek zupełne zrezygonowanie z przechowywania sensownej zawartości w samym editboksie i użycie do tego jakiegoś zewnętrznego bufora w postaci zmiennej. To jednak wymaga obsługi komunikatów związanych z całym procesem edycji, aby ten zewnętrzny bufor uaktualniać.

Istnieją oczywiście gotowe implementacje kontrolek, które działają właśnie w ten sposób. Czy warto ich używać? Sądzę, że niekoniecznie – z dwóch powodów.
Po pierwsze, pole tekstowe do wprowadzania hasła nie musi być wcale najwęższym gardłem bezpieczeństwa aplikacji. Niewiele przyjdzie z nawet najlepszego zabezpieczenia go, jeśli potem wpisane doń hasło jest np. przesyłane otwartym tekstem przez sieć lub zapisywane w pliku w postaci hasha MD5.
Po drugie, użytkownicy są generalnie przyzwyczajeni do jednokrotnego wpisywania haseł, które są następnie zapisywane między sesjami. Tak się dzieje chociażby na stronach WWW, gdzie w tym celu korzysta się z ciasteczek (cookies). Hasła więc bywają zapominane, lecz w sieci nie jest to wielkim problemem ze względu na powszechne mechanizmy przypominania. Tradycyjne aplikacje ich nie posiadają, więc istnienie jakieś metody pozwalającej użytkownikowi na odzyskanie z nich haseł (czyli użycie “odgwiazdkowujących” programów) nie jest wcale rzeczą złą.

A że przy okazji może to zrobić ktoś niepowołany? No cóż… Jeśli posiada on już dostęp do komputera ofiary oraz prawa administratora w jej systemie (bo to jest potrzebne, by wymienione wyżej triki działały), to jest wątpliwe, żeby poprzestał tylko na zabawie z gwiazdkami w editboksach ;-)

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

10 lat w sieci

2009-12-25 20:46

Tegoroczny koniec grudnia to czas podsumowań tego, co działo się nie tylko w ciągu ostatnich 12 miesięcy, ale i całej ostatniej dekady. A przypadkiem właśnie dekadę temu zyskałem dostęp do globalnej sieci zwanej Internetem… Jest to więc ciekawa okazja na podzielenie się paroma moimi obserwacjami tego, jak na przestrzeni ostatnich 10 lat Internet się zmieniał.

Rok 1999 to nie były jakieś zamierzchłe czasy sieci w Polsce (nie wspominając już o sieci w ogóle), lecz mimo to podejrzewam, że niezbyt duża część obecnych jej użytkowników dobrze je pamięta. Cechą wyróżniającą Internetu w tamtym okresie była przede wszystkim oszczędność. Szerokopasmowe łącza nikomu się wtedy nie śniło, a część internautów musiała płacić za sam czas spędzony w sieci (sławetny numer 0202122). Dlatego też podstawową zasadą netykiety było możliwie najskuteczniejsze zmniejszanie rozmiarów przesyłanych danych. Objawiało się to dość spartańskim wyglądem ówczesnych stron, z rzadka składającym się z większej ilości obrazków, a często wykorzystującym takie niefortunne wynalazki HTML jak choćby ramki.
Jak nietrudno zauważyć dzisiaj jest zupełnie inaczej. Główna strona typowego portalu może spokojnie ważyć kilka megabajtów i być okraszona kilkoma reklamowymi animacjami typu Flash, często zresztą niczym nie różniących się od spotów telewizyjnych. Wymaga ona więc znacznie większej przepustowości łącza, a także o wiele lepszej i efektywniejszej przeglądarki.

To też zresztą znak rozpoznawczy Internetu końca dekady: jest on niemal tożsamy z WWW, a inne protokoły komunikacji są w głębokim odwrocie. Kilka lat wcześniej było pod tym względem inaczej. Przeglądanie stron było tylko jedną z wielu sieciowych aktywności, do których należało też korzystanie z e-maila, ściąganie plików przez FTP, czytanie grup dyskusyjnych (Usenet) czy pogaduszki za pośrednictwem IRC. Dzisiaj prawie każdy z tych kanałów komunikacji został pochłonięty lub zastąpiony przez tzw. aplikacje webowe, czyli po prostu trochę bardziej interaktywne wersje stron WWW.

I wreszcie trzeci aspekt zmian, jakie w sieci się dokonały: jej umasowienie. Nie chodzi tu tylko o sam wzrost liczby osób korzystających z sieci, który w ciągu ostatnich 10 lat był prawie czterokrotny. Chodzi o pewne zmiany w samej “strukturze” sieci, które są przynajmniej częściowo jego wynikiem.
Kiedyś w Przekroju czytałem artykuł reklamowany na okładce wiele mówiącą frazą: “Jak idioci zepsuli nam Internet”. Dotyczy ona oczywiście całego sieciowego trendu znanego pod ogólnym określeniem Web 2.0, nad którym zresztą pastwiłem się już przy innej okazji. W skrócie chodzi mniej więcej o to, że połączenie masowości dostępu do Internetu (w krajach rozwiniętych to już przynajmniej połowa populacji) z łatwością umieszczania w nim nowych treści (coraz mniejsza konieczność znajomości takich “technikaliów” jak choćby HTML) powoduje zalanie sieci contentem bez żadnej wartości, czyli jej zwyczajne zaśmiecenie.
W sumie jednak jest to nic nowego; już przed 10 laty mówiło się, że zawartością Internetu są w większości bzdury. Różnica między stanem z tego czasu a dniem dzisiejszym jest jednak taka, że mimo bezwzględnego (a pewnie i względnego) wzrostu ilości treściowych odpadów znalezienie poszukiwanych informacji jest paradoksalnie łatwiejsze niż kiedykolwiek wcześniej. Zawdzięczamy to jednak tylko i wyłącznie gwałtownemu rozwojowi internetowych wyszukiwarek.

Ogólny bilans tej dekady w sieci nie jest więc jednoznaczny. Z jednej strony stoją rzecz jasna MySpace, Facebooka, 4chana czy inne sieciowe szkodniki, ale z drugiej jest przecież Google, Wikipedia i całe mnóstwo wartościowych serwisów tematycznych, z których część nie powstałaby pewnie w warunkach “elitarnego” Internetu sprzed dekady.
Jesteśmy więc w innym miejscu – lecz niekoniecznie gorszym. Trzeba się do niego po prostu przyzwyczaić :)

Tags: ,
Author: Xion, posted under Internet, Life, Thoughts » 1 comment

Niekończące się liczby

2009-12-22 14:18

Symbol nieskończonościNieskończoność jest jednym z tych pojęć matematycznych, które z samej natury nie dają się odnieść do codziennego życia. W informatyce chyba najbliższym do niej odniesieniem jest nieskończona pętla – czyli taka, której warunek stopu nigdy nie będzie prawdziwy. Wiadomo jednak, że w rzeczywistości ową pętlę prędzej czy później przerwie jeśli nie debugujący kod programista, to jakiekolwiek inne zdarzenie z gatunku losowych, z brakiem prądu włącznie.
Dlatego też intuicyjne pojmowanie tego pojęcia często bywa nieadekwatne do jego faktycznego, matematycznego znaczenia. Można się było o tym przekonać niedawno za sprawą pewnego wątku na forum Warsztatu.

0,(9), czyli 1Dotyczył on pewnego matematycznego faktu – iż liczba 0,(9) równa się po prostu 1. Zapis 0,(9) oznacza tutaj – zgodnie z przyjętą powszechnie konwencją – ciąg 0,999… nieskończonej liczby dziewiątek w rozwinięciu dziesiętnym liczby.
Powyższa równość bywa często kwestionowana przez osoby niezbyt dobrze zaznajomione z bardziej abstrakcyjnymi koncepcjami matematycznymi, jak chociażby granice, szeregi czy własności zbioru liczb rzeczywistych. “Intuicyjnie” – aczkolwiek zupełnie błędnie – może się wydawać, że 0,(9) nie jest równe jeden, a jest jedynie liczbą bardzo, bardzo jej bliską. Najlepiej tak bliską, że już żadnej bliższej być nie może.

To oczywiście jest kompletną bzdurą, bo taka liczba nie istnieje. Jedną z najważniejszych cech liczb rzeczywistych jest właśnie to, że:

\displaystyle \forall a,b \in \mathbb{R}, a < b \  \exists x \in \mathbb{R} \quad a< x < b

czyli że dla dowolnych dwóch takich liczb zawsze da się znaleźć trzecią, dającą się “włożyć” między nimi. Co więcej, takich pośrednich wartości jest całkiem sporo, bo dokładnie tyle samo co w całym zbiorze \mathbb{R} – nieskończenie wiele.
I tu właśnie już wyraźnie pojawia się nieskończoność, która – jak sądzę – jest głównym “winowajcą” stwierdzeń, że jakoby 0,(9) \ne 1. Gdy spotykamy się z nim, dość naturalną reakcją jest bowiem zapytanie, ile wobec tego wynosi różnica |1 - 0,(9)|. Następująca dalej próba zapisania rzekomej “liczby tak bliskiej zera, że już bliższej nie ma” kończy się następnie wyprodukowaniem potworka w rodzaju 0,(0)1. W założeniu ma być on odczytywany jako liczba, która w zapisie dziesiętnym ma nieskończoną liczbę zer, po których następuje jedna jedynka…

Chwila! Jak w ogóle coś może występować na końcu nieskończonego ciągu?!… Ano właśnie – tutaj następuje efektowna logiczna sprzeczność, która ładnie dowodzi nieprawidłowości założenia (czyli nieszczęsnej równości 0,(9) \ne 1). Pokazuje też, że mamy tu w gruncie rzeczy do czynienia z nierozumieniem pojęcia tzw. nieskończoności potencjalnej, czyli najprostszego ujęcia idei nieskończoności: jako takiej ilości, która jest większa od każdej, dowolnie dużej skończonej liczby.
A to przecież nie wszystko, co matematyka na temat nieskończoności ma do powiedzenia. Wręcz przeciwnie – to dopiero marny wycinek, jako że teoria zbiorów przekonuje nas, że tak naprawdę rodzajów nieskończoności jest… nieskończenie wiele :) Uff, jak dobrze, że w informatyce wszystko, na czym się w praktyce operuje jest z założenia skończone i możliwe do policzenia… A jeśli nawet nie, to przecież zawsze można się ograniczyć do jakiegoś ograniczonego z góry zbioru wartości, które wspieramy. Tak jak pewien protokół używany do routowania, dla którego nieskończoną liczbą jest już… 16 :]

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

Unikalne łańcuchy znaków

2009-12-19 12:25

Okazjonalnie zachodzi potrzeba, aby do jakiegoś celu wygenerować niepowtarzalny ciąg znaków. Jednym z zastosowań może być tutaj nadanie plikowi nazwy gwarantującej brak kolizji z jakąkolwiek inną nazwą pliku w systemie albo chociaż w aktualnym katalogu. W przypadku gdy ów plik ma istnieć tylko przez chwilę – a więc być plikiem tymczasowym (tempfile), sprawa jest prosta i sprowadza się do wywołania GetTempFileName jako funkcji WinAPI lub metody klasy System.IO.Path z .NET.

Stworzona w ten sposób nazwa jest z definicji “krótko żyjąca”. Na drugim biegunie w zakresie unikalności znajdują się tzw. UUID-y, czyli specjalnie ustandaryzowane 128-bitowe identyfikatory konstruowane tak, by zapewnić, że prawdopodobieństwo wygenerowania duplikatów jest mikroskopijnie małe. Każdy, kto grzebał trochę w Rejestrze Windows lub miał do czynienia z technologią COM, zna na pewno ich kuzynów, czyli GUID-y, opartych w gruncie rzeczy na podobnych zasadach.
Ponieważ tworzenie nowych GUID-ów/UUID-ów jest proste, można je wykorzystać w celu uzyskania niepowtarzalnych ciągów znaków. W czystym Windows API unikalny string oparty na UUID-zie można otrzymać np. tak:

  1. #include <rpc.h>
  2. #pragma comment(lib, "rpcrt4.lib")
  3. string RandomStringViaUUID()
  4. {
  5.     UUID uuid;
  6.     if (UuidCreate(&uuid) != RPC_S_OK) return string();
  7.  
  8.     char* uuidStr;
  9.     if (UuidToStringA(&uuid, (unsigned char**)&uuidStr) != RPC_S_OK)
  10.         return string();
  11.     string res(uuidStr);
  12.     RpcStringFree (&uuidStr);
  13.     return res;
  14. }

Z kolei w .NET istnieje wyspecjalizowana klasa System.Guid, którą można wykorzystać w podobnym celu. W wyniku otrzymamy oczywiście teksty w stylu:

0DF44EAA-FF21-4412-828E-260A8728E7F1

co w wielu zastosowaniach może być strzelaniem z armaty do komara. Nie zawsze potrzebujemy przecież aż tak wielkiej unikalności, a UUID-y w formie tekstowej są długie i nieporęczne.

Są więc i inne rozwiązania, stosowalne jeśli nasz losowy ciąg musi być unikalny tylko przez stosunkowo niedługi czas, np. podczas jednego uruchomienia programu. W takim wypadku możemy użyć chociażby:

  • wartości zwracanych przez GetTickCount/QueryPerformanceCounter lub asemblerową instrukcję rdtsc. Jest to czas (lub liczba cykli procesora) od momentu rozruchu systemu, więc z definicji wartość taka nie powtórzy się w trakcie działania aplikacji. Warto wiedzieć aczkolwiek, że używanie rdtsc jest generalnie niezalecane.
  • aktualnego systemowego czasu – pobranego przez GetSystemTime z Windows API czy System.DateTime.Now z .NET – przekonwertowanego na string. Wadą jest tutaj wynikowa długość łańcucha i zbyt oczywisty format; prawdopodobnie więc należałoby jeszcze poddać go jakiemuś post-processingowi.
  • zwykłego ciągu losowych znaków. W zależności od jakości generatora liczb losowych oraz losowości początkowego ziarna seed, możemy otrzymać nawet całkiem dobre wyniki. Metoda jest dobra zwłaszcza wtedy, gdy generowanych identyfikatorów jest na tyle mało, że możemy je efektywnie przechowywać i po prostu sprawdzać, czy nowy string jeszcze nie wystąpił (przykładem mogą być uchwyty w menedżerze zasobów w silniku gry).

Najważniejsze jest to, aby właściwie dobrać metodę do sytuacji, w której potrzebujemy unikalnego łańcucha znaków. Często wystarczy coś w stylu:

  1. string s;
  2. while (rand() % MAX_LENGTH - s.length() > 0)
  3.     s += rand() % ('Z' - 'a') + 'a';

Nie myślmy nawet jednak, żeby tak stworzonego stringa używać do jakiejkolwiek formy komunikacji przez sieć. W takich przypadkach trzeba w zasadzie użyć UUID-ów.

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

Dlaczego nie lubię typów referencyjnych

2009-12-16 11:54

Jeśli chodzi o C++, to nietrudno zauważyć, że często pozwalam sobie na sporo uwag krytycznych pod adresem tego języka. Oczywiście zawsze jest to krytyka konstruktywna :) Tym niemniej wiele jest tu rzeczy, o których można powiedzieć, że w innych językach zostały pomyślane lepiej (łącznie z takimi, których w C++ nie ma, a przydałyby się).
Dlatego dzisiaj będzie trochę nietypowo. Chcę bowiem wspomnieć o problemie, który w językach pokroju C# czy Javy potrafi doprowadzić do powstawania trudnych do wykrycia błędów – i który jednocześnie w C++ w zasadzie nie występuje wcale.

Mam tu na myśli semantykę referencji, czyli pewien szczególny sposób odwoływania się do szeroko rozumianych obiektów w kodzie. Klasy, a właściwie to prawie wszystkie typy poza podstawowymi (jak liczby czy znaki), są w C# i Javie obsługiwane w ten właśnie sposób; dlatego czasami nazywa się je typami referencyjnymi.
Najważniejszą cechą takich typów jest fakt, że należące do nich zmienne nie zawierają bezpośrednio instancji obiektów. Jeśli na przykład Foo jest klasą, to deklaracja:

  1. Foo x;

nie sprawi, że pod nazwą x będzie siedział obiekt typu Foo. x będzie tutaj zaledwie odwołaniem do takiego obiektu – w tym przypadku zresztą odwołaniem pustym, niepokazującym na nic.
Jest to zachowanie diametralnie różne od typów podstawowych, jak choćby int. Ale idźmy dalej – skoro mamy zmienną mogącą trzymać odwołanie (czyli referencję) do obiektu, to pokażmy nią na jakiś obiekt, na przykład taki zupełnie nowy:

  1. x = new Foo();

A że w prawdziwym programie zmiennych i obiektów jest zawsze mnóstwo, to wprowadźmy na scenę jeszcze parę:

  1. Foo y = x;
  2. y.SomeValue = 4; // hmm...

No i zonk, można powiedzieć… Nikt aczkolwiek tego nie powie, bo dla każdego programisty C#, Javy itp. istnienie wielu referencji do tego samego obiektu jest rzeczą całkowicie naturalną. Jednak wiem, że podobny kod dla dowolnego typu liczbowego (zastąpiwszy ostatnią linijkę przez y += 4; lub coś tym w guście) zachowałby się zupełnie inaczej. Wiem też, że kiedyś byłem zmuszony wykonać kilka empirycznych testów, by się o tym naocznie przekonać; było to jeszcze w Delphi, a powodem były oczywiście jakieś “dziwne” błędy, na które natrafiłem w jednym ze swoich programów. Źle użyte typy referencyjne łatwo mogą być bowiem przyczyną takich błędów, które zresztą bywają potem trudne do wykrycia.

Bez jakiegoś rodzaju referencji nie da się rzecz jasna wyobrazić sobie użytecznego języka programowania. Sęk w tym, że w C# czy Javie używanie ich nie jest opcją do stosowania w tych przypadkach, które tego wymagają – jest koniecznością wymuszoną przez sam fakt programowania z użyciem klas i obiektów. To całkiem inaczej niż w C++, gdzie w tym celu trzeba wyraźnie zaznaczyć swoje intencje (najczęściej poprzez użycie typów wskaźnikowych).
W tworzeniu oprogramowania istnieje tzw. zasada najmniejszego zdziwienia (principle of least astonishment). Mówi ona, że przy alternatywie równoważnych przypadków powinno się wybrać ten, który u użytkownika końcowego będzie powodował mniejsze zdziwienie. Czy typy referencyjne zachowujące się zupełnie inaczej niż typy podstawowe i “same” zmieniające swoją zawartość nie są przypadkiem złamaniem tej reguły?…

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

Naprawianie IntelliSense

2009-12-13 20:11

Wbudowany w Visual Studio system autouzupełniania kodu w przypadku C++ często działa całkiem dobrze, ale wiele osób pewnie stwierdziłoby, że częściej nie działa w ogóle. Każdy na pewno spotkał się z sytuacją, że po wpisaniu operatora kropki czy strzałki (->) jego oczom nie ukazywała się wcale lista składników klasy, a jedynie mało pomocny komunikat na pasku stanu. Sugeruje on zapoznanie się z określonym artykułem w MSDN, traktującym o rozwiązywaniu problemów z IntelliSense.

Przyznajmy teraz: czy ktoś rzeczony rzeczywiście artykuł przeczytał? :) Mam co do tego spore wątpliwości, lecz od razu zapewniam: nic straconego. Nie ma tam bowiem nic specjalnie użytecznego ;> Nie istnieje niestety żaden specjalny trik, który sprawiłby, aby mechanizm autouzupełniania w VS dla kodu C++ działał co najmniej w połowie tak dobrze jak chociażby dla C#. Jeśli więc chcemy, by był on dla nas choć trochę użyteczny, musimy sami mu pomóc. Jak?

  • Poprawmy błędy kompilacji. Dotyczy to zwłaszcza błędów w nagłówkach (jako że są one dołączane wielokrotnie w różnych plikach .cpp) oraz błędów powodujących trudności w określeniu, gdzie kończą się poszczególne fragmenty kodu (czyli wszelkiego rodzaju nieprawidłowych nawiasowań). Drobne pomyłki w rodzaju literówek w identyfikatorach wpływają zwykle tylko na tę linijkę kodu, w której występują, a po korekcie IS potrafi bardzo szybko wrócić do działania.
  • Uważajmy na preprocesor i nagłówki. MSDN jawnie specyfikuje jedną sytuację, w której IntelliSense nie musi działać. Dotyczy to wielokrotnego dołączania tego samego pliku nagłówkowego w taki sposób, że makra (#define‘y) wpływają na sposób, w jaki jest on przetwarzany. Przykładem może być dołączenie windows.h raz normalnie, a drugi raz z uprzednio zdefiniowanym makrem WIN32_LEAN_AND_MEAN.
  • Jeśli wszystko zawodzi, wygenerujmy od początku plik .ncb. W tym pliku (często największym w całym projekcie) IntelliSense przechowuje informacje o strukturze kodu. Jeśli często “rozgrzebujemy” nasz projekt, czynimy w nim duże zmiany i dokonujemy refaktoringu, to plik ten może zawierać nieaktualne, nadmiarowe lub nieprawidłowe informacje albo po prostu być uszkodzony. W takim wypadku należy go po prostu usunąć, a następnie przeprowadzić ponowne kompilację całego solution.

Nie ma oczywiście gwarancji, że powyższe kroki sprawią, że IntelliSense poradzi sobie z podpowiadaniem w każdej sytuacji. Kod C++, zwłaszcza skomplikowany i korzystający z wielu “sztuczek językowych” (jak np. bibliotek Boost) nie jest łatwy do programowej analizy. Pozostaje więc mieć nadzieję, że w przyszłych wersjach VS z autouzupełnianiem będzie już lepiej; podobno w wersji 2010 jest z tym już całkiem nieźle :)

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


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