Archive for Programming

C++11 est arrivé!

2011-08-20 1:38

O ile tylko ktoś nie spędził zeszłego tygodnia na Antarktydzie, w amazońskiej dżungli czy w innym podobnie odciętym od cywilizacji miejscu, z pewnością słyszał najważniejszą nowinę ostatnich lat. A już na pewno wspomnianego tygodnia – bo przecież jak tu ją nawet porównywać z takimi błahostkami jak choćby zakup Motoroli przez Google. Przecież mówimy tutaj o pomyślnym końcu procesu rozpoczętego w czasach, gdy Google nawet nie istniał! To musi robić wrażenie… I nawet jeśli owym wrażeniem jest głównie: “No wreszcie; co tak długo?!”, to przecież w niczym nie umniejsza to rangi wydarzenia.

Tak, mamy w końcu nowy standard C++! I to w sumie mogłoby wystarczyć za całą notkę, bo chyba wszystko, co można by powiedzieć na temat kolejnej wersji jednego z najważniejszych języków programowania, zostało już pewnie dawno powiedziane w dziesiątkach serwisów informacyjnych, tysiącach blogów i milionach tweetów. Znaczącą ich część zajmują omówienia nowych możliwości języka, dostępnych zresztą od jakiegoś czasu (acz w niepełnej formie) w kilku wiodących kompilatorach. Możliwości tych jest całkiem sporo i dlatego nie mam zamiaru nawet wyliczać ich wszystkich. Zdecydowałem, że w zamian przyjrzę się bliżej tylko trzem z nich – tym, które uważam za najbardziej znaczące i warte uwagi.

Etykiety też mają adresy

2011-08-12 21:25

Natrafiłem niedawno na kapitalną ciekawostkę, sponsorowaną przez literkę C – język C, rzecz jasna. A jeśli już o C mowa, to jednym z pierwszym skojarzeń są oczywiście wskaźniki; prawdopodobnie nie jest ono zresztą specjalnie pozytywne ;) Wśród nich mamy wskaźniki na funkcje, które znane są z kilku nieocenionych zastosowań (weźmy na przykład funkcję qsort), ale przede wszystkim z pokrętnej i niezbyt oczywistej składni.

Zalecam jednak przypomnienie jej sobie, bo dzisiaj będzie właśnie o wskaźnikach na funkcje, tyle że pokazujących na… etykiety. Tak, te właśnie etykiety (labels), które normalnie są celem instrukcji gotozłej, niezalecanej, i tak dalej. Okazuje się bowiem, że etykiety też mają swoje adresy, które w dodatku można pobrać i wykorzystać jako wskaźniki:

  1. #include <stdio.h>
  2.  
  3. int main() {
  4.     first:
  5.     second:
  6.     printf("first = %p, second = %p, last = %p", &&first, &&second, &&last);
  7.     last:
  8.     return 0;
  9. }

Wymagany jest do tego podwójny znak ampersandu (&), co można traktować jako osobny operator lub rodzaj specjalnej składni… W każdym razie jest on konieczny, aby kompilator wiedział, że następująca dalej nazwa jest etykietą. Mają one bowiem swoją własną przestrzeń nazw, co oznacza, że możliwe jest występowanie np. zmiennej start i etykiety start w tym samym zasięgu.
Przykładowym wynikiem działania programu jest poniższa linijka:

first = 0x4004f8, second = 0x4004f8, last = 0x400519

Pierwsze dwa adresy są sobie równe i nie powinno to właściwie być zaskakujące. Jak można się bowiem łatwo domyślić, adresy etykiet to w istocie adresy instrukcji, które są nimi opatrzone. Dla osób programujących w asemblerze powinno być to dziwnie znajome :) W powyższym przykładzie zarówno first, jak i second mają adresy odpowiadające położeniu w pamięci kodu wywołania funkcji printf.

Wróćmy jednak do wspomnianych wcześniej wskaźników na funkcje. Mając bowiem pobrany adres etykiety, możemy go przypisać do takiego właśnie wskaźnika. Dzięki temu możemy etykiety możemy “wywoływać”, i to nawet z innych funkcji!
Jak to jednak w ogóle działa? Ilustruje to poniższy przykład, w którym funkcja wywołuje w ten sposób sama siebie:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3.  
  4.  
  5. typedef int (*find_func)(int*, int, int);
  6. int find(int* tab, int n, int x) {
  7.     int i = 0;
  8.     top:
  9.     if (n == 0) return -1;
  10.     else {
  11.         if (*tab == x)  return i;
  12.         ++i; ++tab; --n;
  13.  
  14.         find_func tail_call = (find_func)&&top;
  15.         tail_call(tab, n, x);
  16.         return -2;  // nieosiągalne
  17.     }
  18. }
  19.  
  20. int main(int argc, char* argv[]) {
  21.     int tab[10] = { 2, 3, 6, 4, 8, 3, 9, 1, 7, 0 };
  22.     printf ("Element '%d' znaleziony na pozycji %d\n", 1, find(tab, 10, 1));
  23. }

Po pobieżnym przyjrzeniu się widać, że jest to zwykłe liniowe przeszukiwanie tablicy, w dodatku w sposób który wygląda na rekurencyjny… Jest to jednak specjalny rodzaj tzw. rekurencji ogonowej (tail recursion). Występuje ona wówczas, gdy wywołanie rekurencyjne jest ostatnią instrukcją funkcji. Taki przypadek może być wówczas zoptymalizowany przez co sprytniejsze kompilatory poprzez wyeliminowanie konieczności ponownego odkładania argumentów na stos. Kolejny poziom rekursji wykorzystuje po prostu te same argumenty – jest to możliwe, o ile nie ma konieczności rekurencyjnych powrotów i składania kolejnych wyników.
W powyższym przykładzie rekursja ogonowa występuje jednak niezależnie od jakichkolwiek optymalizacji, gdyż jest ona zawarta w “wywołaniu” etykiety top. Chociaż pozornie wygląda to jak wywołanie funkcji, nie powoduje utworzenia dodatkowej ramki stosu ani ponownego odłożenia na nim argumentów. Docelowa “funkcja” operuje na istniejącym stosie. W istocie więc mamy tu do czynienia z pewną wersją instrukcji goto, czyli zwykłego skoku.

Ciekawiej zaczyna się robić wtedy, gdy spróbujemy “wywołać” etykietę z innej funkcji, co – jak wspomniałem – jest zupełnie możliwe. Możliwe jest wówczas gładkie przekazanie jej wszystkich parametrów oraz zmiennych lokalnych, co automatycznie podpada pod kategorię Rzeczy Podejrzanych i Potencjalnie Niebezpiecznych :) Niemniej jednak jest to możliwe:
#include

int foo = 1, i;

typedef int (*args_func)(int argc, char* argv[]);
args_func process(int argc, char* argv[]) {
if (foo == 1) {
return &&inside;
}
if (foo == -1) {
inside:
for (i = 1; i < argc; ++i) printf("Arg #%d: %s\n", i, argv[i]); } return 0; } int main(int argc, char* argv[]) { process(0, NULL)(0, NULL); // sic }[/c] Powyższy program przekazuje swoje argumenty do funkcji process (która je wypisuje) mimo iż zupełnie tego nie widać w jej wywołaniu. Zresztą normalne wywołanie tej funkcji jedynie zwraca adres jej etykiety, pod który później skaczemy z maina bez dokładania nowej ramki do stosu.

W tym kodzie jest też kilka innych smaczków (jak chociażby rola zmiennej foo), których odkrycie pozostawiam jednak co bardziej dociekliwym czytelnikom :) Zaznaczę tylko, że cała ta (chyba) niezbyt praktyczna zabawa z pobieraniem adresów etykiet jest w gruncie rzeczy rozszerzeniem GCC i nie jestem pewien, czy będzie działać w jakimkolwiek innym kompilatorze.

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

Implementacja przeciągania

2011-08-08 20:19

Przedstawię dzisiaj dość elementarną technikę, związaną z szeroką pojętym programowaniem grafiki – a zwłaszcza interfejsów użytkownika. Chodzi o nic innego jak przeciąganie (dragging), czyli przemieszczanie różnych elementów za pomocą myszy czy innego urządzenia wskazującego (np. palca ;]). Celowo nie dodałem tutaj drugiej połowy procesu znanego jako drag & drop – czyli upuszczania – bo nie mam w ogóle na myśli potencjalnie zaawansowanej logiki związanej z wymianą danych reprezentowanych przez przeciągany obiekt. Przeciwnie; chodzi mi wyłącznie o samo przesuwanie go po ekranie. Rezultatem (niekoniecznie jedynym, rzecz jasna) ma być po prostu zmiana jego położenia.

W zaawansowanych systemach GUI rozwiązanie sprowadza się najczęściej do instrukcji w rodzaju obj.Draggable = true;, a cała reszta odbywa się “automagicznie”. Załóżmy jednak, że nie mamy zaawansowanego systemu GUI i dysponujemy jedynie możliwością rysowania oraz odbierania zdarzeń od wskaźnika myszy. Nie jest to wcale rzadka sytuacja: żeby nie szukać daleko, wystarczy cofnąć się o tydzień do opisu elementu <canvas> z HTML5 :) W ogólności dotyczy to jakiejkolwiek “czystej” biblioteki graficznej: OpenGL, DirectX, SDL, itp.
Doprecyzujmy jeszcze to, iż “odbieranie zdarzeń od myszy” obejmuje tak naprawdę poniższe trzy zdarzenia (lub ich odpowiedniki dla innych – np. dotykowych – sposobów wskazywania):

  • wciśnięcie przycisku myszy – zdarzenie odbierane zwykle jako MouseDown
  • ruch wskaźnikiem – MouseMove
  • zwolnienie przycisku – MouseUp

Nietrudno zauważyć, że całkowicie wystarczają one do implementacji przeciągania. Jak więc miałaby ona wyglądać?

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

Canvas, czyli programowanie grafiki w HTML

2011-07-31 0:09

Spośród wielu nowości wprowadzonych w HTML5, API do rysowania dwuwymiarowej grafiki rastrowej bez użycia wtyczek typu Flash jest z pewnością jednym z najbardziej interesujących. Wielu zresztą twierdzi (wliczając w to prominentne figury świata IT), że zwiastuje to początek końca wspomnianych pluginów. Czy rzeczywiście tak będzie, to oczywiście zobaczymy w niedalekiej przyszłości. Już teraz jednak można zobaczyć liczne przykłady na to, że technologia ta oferuje naprawdę spore możliwości.

W ramach zapoznawania się z poszczególnymi składowymi HTML5, nie mogłem więc nie przyjrzeć się bliżej elementowi <canvas> – bo to o nim oczywiście mowa. Tradycyjnym testem dla tego rodzaju rozwiązań (tj. graficznych bibliotek 2D), który zawsze staram się zaimplementować na początku, jest… chmara odbijających się piłeczek :) Nie inaczej było i w tym przypadku, czego efekty zamieszczam tutaj wraz z krótkim opisem i wprowadzeniem w podstawy Canvasa.

Automatyzacja językami programowania

2011-07-28 21:11

Zwykłego użytkownika od power usera dobrze odróżnia sposób radzenia sobie z powtarzalnymi zadaniami. Perspektywa zmiany nazwy N plików, skonwertowania N obrazków czy skatalogowania N utworów muzycznych jest odstręczająca już dla niewielkich wartości N, jeśli mielibyśmy wykonywać te czynności ręcznie. Zaawansowany użytkownik w tym celu zakasze jednak rękawy i wysmaży odpowiedni skrypt, który może nie będzie działał od razu, ale za to w końcu poradzi sobie z zadaniem całkowicie automatycznie. Niekoniecznie musi to w sumie zająć mniej czasu niż procedura ręczna, ale na pewno będzie mniej męczące :)

Dlatego też niemal zawsze staram się wybierać programową automatyzację. Wiąże się z tym jednak pewien problem. Otóż języki powłok systemowych (shelli) to nie jest coś, z czym koder ma intensywny kontakt na co dzień. Należą one raczej do obszaru zainteresowań administratorów. W związku z tym wyprodukowanie jakiegoś działającego kawałka skryptu jest często poprzedzone co najmniej krótkim przypominaniem sobie składni i semantyki danego języka. Zasadniczo jest to strata czasu lub – wyrażając się nieco inaczej – czynnik zwiększający minimalną wartość N, od której automatyzacja ma sens.

Ale jeśli nie języki shellowe, to co? Ano to, czego używamy na co dzień, czyli zwykłe języki programowania. Tu niestety nie ma sprawiedliwości: niektóre z nich nadają się do zadania nieporównywalnie lepiej niż inne. Część tych drugich ma swoje interpretowane, skryptowe wersje; przykładem jest choćby javowy BeanShell. Ich odpowiedniość do wersji pełnych nie jest jednak wcale zapewniona. Inne języki zwyczajnie nie mają podobnych narzędzi i zostawiają nas z koniecznością wyprodukowania kompletnego programu.
Tutaj ujawnia się przewaga Perla, Pythona, Ruby’ego i podobnych im języków interpretowanych, które nie wymagają do uruchomienia niczego poza plikiem z “czystym” kodem. Jest to dokładnie taka sama sytuacja, jak w przypadku basha czy innych języków powłoki. Korzyść jest jednak oczywista, jeśli tylko któryś z tych języków jest nam znany: nie ma tu bariery składniowej.

Nie znaczy to oczywiście, że jeśli ktoś potrafi jednym zaklęciem złożonym z ls, xargs i grepa przetworzyć tysiąc plików tekstowych, to powinien porzucić tę sztukę i zwrócić się ku Prawdziwemu Programowaniu™. Zapewne też dobry programista będzie potrafił w końcu wyprodukować ową magiczną formułę, pod warunkiem spędzenia odpowiednio długiego czasu nad stronami mana. Jeśli jednak alternatywą jest napisanie kilkunastu linijek w znanym sobie języku, które zrobią to samo, będą miały spore szanse działać za pierwszym razem i zajmą w sumie co najwyżej kilka minut… to czemu nie? Warto korzystać ze swoich umiejętności nie tylko w ich ściśle ograniczonym obszarze zastosowań.

A jeśli przypadkiem ktoś właśnie zechciał akurat nauczyć się któregoś z wymienionych ze mnie języków, to… tak, polecam Pythona :)

Obsługa strumienia w Javie

2011-07-26 20:37

W językach kompilowanych mechanizmy wejścia-wyjścia opiera się niemal zawsze o koncepcję strumienia (stream), czyli abstrakcyjnego obiektu z którego możemy czytać dane i/lub je do niego zapisywać. Ponieważ strumień jest opakowaniem na jakiś zewnętrzny zasób – plik, połączenie sieciowe, itp. – należy generalnie dbać o jego poprawne i szybkie zamknięcie, gdy nie jest już potrzebny. Dotyczy to także zwłaszcza języków z zarządzaną pamięcią, gdzie osierocony obiekt strumienia może nie być posprzątany przez bardzo długi czas, zajmując zewnętrzne, niezarządzane zasoby systemowe.

Poprawny sposób postępowania z obiektem strumienia, który chciałem dzisiaj omówić, dotyczy konkretnie języka Java, gdyż tam cała sprawa jest co najmniej nietrywialna. Dzieje się tak z trzech powodów:

  • W Javie każdy wyjątek musi zostać albo złapany (catch), albo zadeklarowany jako opuszczający funkcję (throws). Jest to sprawdzane podczas kompilacji.
  • Metoda close, zamykająca strumień, deklaruje potencjalne wyrzucanie wyjątku IOException.
  • Java poniżej wersji 7 nie posiada odpowiednika konstrukcji with (obecnej np. w C# i Pythonie), która automatycznie posprzątałaby po obiekcie strumienia w momencie opuszczenia jej zasięgu.

Brak instrukcji with sprawia, iż do dyspozycji pozostaje nam wyłącznie try-catch lub try-finally. Naiwne zastosowanie któregoś z nich nie daje jednak pożądanych efektów:

  1. try {
  2.     InputStream is = new FileInputStream("file.txt");
  3.     // ...
  4. } finally {
  5.     is.close(); // ups!
  6. }

Takim efektem byłby na przykład fakt kompilowania się kodu :) W tej wersji jest to jednak niemożliwe (o ile funkcja nie deklaruje wyrzucania IOException), bowiem wyjątek ten może zostać rzucony przez metodę close… A przynajmniej taka jest teoria, którą kompilator niestety pedantycznie sprawdza.

W rzeczywistości ten kod ma przynajmniej jeszcze jeden błąd, którego nie wyeliminuje otoczenie wywołania close odpowiednim blokiem try-catch. Jego znalezienie pozostawiam aczkolwiek jako – ahem – ćwiczenie dla czytelnika ;) W zamian pokażę dla odmiany nieco lepszy sposób na obejście zaprezentowanych problemów.
Polega on na zastosowaniu dwóch zagnieżdżonych bloków try: jednego z catch do złapania IOException i drugiego z finally do zamknięcia strumienia. W całości prezentuje się to następująco:

  1. try {
  2.     InputStream is = new FileInputStream("file.txt");
  3.     try {
  4.         // (czytamy ze strumienia)
  5.     } finally {
  6.         is.close();
  7.     }
  8. } catch (IOException e) {
  9.     // ...
  10. }

Przy zastosowaniu takiej konstrukcji wszystkie miejsca, w których wyjątek I/O może wystąpić, są otoczone blokiem try-catch, więc kompilator nie będzie miał powodów do narzekań. Nadal też gwarantujemy, że strumień zostanie zawsze zamknięty, co z kolei zapewnia blok try-finally.

A że wygląda to wszystko cokolwiek nieestetycznie? Cóż… Java :)

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

HTML nr 5 – książka

2011-07-23 14:53

Właśnie skończyłem lekturę książki Wprowadzenie do HTML5 autorstwa Bruce’a Lawsona i Remy’ego Sharpa, wydanej w wersji polskiej przez Helion. Zakupiłem ją z nadzieją, że wprowadzi nieco porządku w cały ten buzz odnośnie HTML5, którego trudno nie zauważyć, jeśli tylko robi się cokolwiek chociaż pośrednio związanego z tą tematyką. I od razu mogę stwierdzić, że pod tym względem moje oczekiwania zostały rzeczywiście spełnione.

W książce można bowiem znaleźć przegląd kilku elementów składających się na standard HTML5 oraz paru technologii pobocznych, które formalnie do standardu nie należą, ale często są uwzględniane pod parasolem szerszej definicji całego zjawiska/technologii/ruchu/trendu/czegokolwiek-czym-to-jest. Wśród nich mamy takie podstawy jak nowe elementy strukturalne, nowe i przedefiniowane znaczniki opisu tekstu, poszerzone możliwości formularzy i różne drobne usprawnienia składniowo-semantyczne. Nie są one może tak ekscytujące jak “gwiazdy” technologii HTML5, ale fakt ich porządnego uwzględnienia jest według mnie tym bardziej godny uwagi.
Oczywiście nie zabrakło też wielu “nowych wspaniałych API”, dzięki którym możemy chociażby tworzyć SQL-owe bazy danych po stronie klienta i wykonywać szereg innych niezbędnych a właściwych dla stron WWW czynności ;D I tak możemy na przykład dowiedzieć się, co to właściwie jest ten Canvas, w jaki sposób zapewnić wsparcie dla techniki drag & drop i do czego w zasadzie mogą przydać się te całe webowe gniazdka (czyli Web Sockets). Ponieważ wszystko pokazane jest na prostych przykładach, opanowanie podstaw każdego z tych rozwiązań dla ogarniętego programisty nie stanowi żadnego problemu.

Kluczowym słówkiem są tu jednak owe ‘podstawy’. Chociaż różne tematy są tu opisane z różnym poziomem szczegółowości – czasem nawet zbyt dużym, jak choćby w przypadku <audio>/<video> – to jednak w żaden nie jest opisany wyczerpująco. Nie wskazuję tego jako wady, bo przecież mówimy o książce z wyraźnym napisem “wprowadzenie” na okładce. Poza tym w prawie każdym miejscu, gdzie autorzy świadomie coś pomijają, podawane są inne (zwykle internetowe) źródła, z których można czerpać bardziej szczegółowe informacje. Trzeba też powiedzieć szczerze, że właściwie żaden z opisywanych tematów (może poza Canvasem) osobno nie prezentuje się jako specjalnie skomplikowany bądź obszerny. Skondensowane wprowadzenie może więc równie dobrze być całkowicie wystarczające.

Podsumowując więc: lektura okazała się jak najbardziej pożyteczna. Książkę czyta się szybko i bez wielkiego wysiłku, a jednocześnie można się z niej dowiedzieć wielu interesujących rzeczy. Jeśli nawet nie zamierzamy – za przeproszeniem – programować w HTML5, to przeczytanie tej książki da nam ciekawy wgląd w technologie będące podobno przyszłością Internetu czy nawet aplikacji jako takich.

Tags: , , ,
Author: Xion, posted under Books, Internet, Programming » 3 comments
 


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