Viimased postitused

Omistamine, kopeerimine ja polümorfism

Kui klass on lihtne, ei pärine ühestki teisest klassist, või kui pärineb ja sinna hulka ei ole segatud polümorfismi, on lihtne otsustada, kas omistamis-operaator ja kopeerimiskonstruktor lisada klassile või mitte. Kui aga polümorfism mängu tuleb, ei ole enam asi nii selge (vähemalt tundub nähtud koodi põhjal nii).

Lihtne reegel oleks see, et keela kopeerimine ja omistamine – deklareeri privaatsena, aga jäta defineerimata omistamis-operaator ja kopeerimis-konstruktor.

C++’is on polümorfisme erinevaid. Järgnevalt tuleb juttu dünaamilisest polümorfismist ehk klassidest, mis sisaldavad virtuaalseid või abstraktseid funktsioone ning tuletatud klassides kirjutatakse need üle ja objekte kasutatakse baasklassi viida või viite kaudu.

Kopeerimine

Miks keelata polümorfsete klasside kopeerimine? Et sellele küsimusele vastata, tuleks vaadata, mida teeb kopeerimis-konstruktor:

struct C : Base
{
    C(C const& other)
        : Base(other)
        , member1(other.member1)
        , ...
    { }
};
struct D : C { ... };

Kopeerimis-konstruktor konstrueerib baasklassi objekti ja kõik klassi liikmed nende kopeerimis-konstruktorite abil. Kui C kopeerimis-konstruktorile anda viide D objektile, siis on probleem, sest sel juhul ei konstrueerita D objekti koopiat vaid selle D objekti C alamobjekti koopia. Kui polümorfismi kasutatakse, siis üldjuhul ei kasutata konkreetseid tüüpe ja probleemid on lihtsad tulema. Aga ilma kopeerimiseta ka päris ei saa ja selleks puhuks on õnneks lahendus olemas – nimelt kloonimine.

Kloonimine

Kloonimine on sisuliselt kopeerimine, ainult et kopeerimis-konstruktori otse kasutamise asemel on vahel virtuaalse funktsiooni välja kutsumine. See virtuaalne funktsiooni väljakutse ongi see, mis võimaldab meil üles leida õige kopeerimis-konstruktori ja objektist õiget tüüpi koopia teha. Tavaliselt kasutatakse sellise funktsiooni nimeks clone(), aga see ei pea nii olema. Selleks, et clone() oma tööd teha saaks, on vaja kopeerimis-konstruktorit, aga see tuleks deklareerida kui protected – privaatseks ei saa teda teha, sest siis ei ole võimalik kopeerida sellest klassist tuletatud klassi objekte ja avalikuks ei ole ka hea mõte teha eelmises peatükis kirjeldatud põhjustel. clone() implementeerimisel tuleks kindlasti jälgida seda, et this viitaks ikka täpselt seda tüüpi objektile, mille klooni parajasti tehakse. Kui see nii ei ole, siis on juhtunud nii, et tuletatud klassi autor on unustanud clone() üle kirjutada. clone() üle kirjutamata jätmine on probleem, sest nagu otse kopeerimis-konstruktori kasutamise puhulgi, võib juhtuda, et kloonitakse ainult baasklassi alam-objekt. Järgnev koodijupp demonstreeribki kõike eelnevalt kirjutatut.

class Base
{
protected:
    Base(Base& other) { ... }
public:
    virtual Base* clone() const
    {
        // derived classes must override clone!
        assert(typeid(*this) == typeid(Base));
 
        return new Base(*this); }
};
 
class D1 : public Base
{
protected:
    D1(D1 const& other) : Base(other) { }
public:
    virtual D1* clone() const override
    {
        // derived classes must override clone!
        assert(typeid(*this) == typeid(D1));
 
        return new D1(*this);
    }
};
 
class D2 : public D1
{
    // viga - clone on üle kirjutamata. Kui seda D2 objekti kloonida,
    // kutsutakse välja D1::clone ja assert ütleb, et probleem on.
};
 
void f(Base& b)
{
     Base* x = b.clone();
     delete x;
}
 
void g()
{
    D1 d1;
    D2 d2;
    f(d1); // 1
    f(d2); // 2
}

Omistamine

Miks omistamis-operaatorit ei saa kasutada?

Põhjus on lihtne – sa ei tea, mis tüüpi objekt on vasakul pool omistamis-operaatorit. Selle peale võib tekkida mõte, et võiks virtuaalse omistamis-operaatori teha, sest siis ju kutsutakse õige omistamis-operaator välja nagu virtuaalsete funktsioonide puhul ikka. Jah, seda küll, aga sa ju ei pruugi teada ka seda, mis tüüpi objekt on paremal pool omistamis-operaatorit. Kui omistamis-operaatoris hakata kontrollima, mis tüüpi objekt paremal pool võrdusmärki on, peaks see operaator kuidagi hakkama saama ka olukorraga, kui see parempoolne objekt ei ole sobivat tüüpi. Kuna omistamis-operaatorist ei ole võimalik ebaõnnestumisest märku anda muul moel kui erindit (i.k. exception) visates, on tulemuseks kood, kus erindid lendavad väga ootamatutel hetkedel. Parem juba eos vältida selliseid asju.

Kui omistamis-operaatorit ikka väga vaja on, siis tuleks vaadata üle oma disain ja leida mõni parem lahendus.

Turvaaugud

Umbes nädal aega tagasi avastasin internetist “sõjamängu” – http://io.smashthestack.org:84/. Tegemist on ühe linuxi masinaga, kus on 29 turvaaukudega programmi. Enamasti on olemas ka nende programmide lähtekoodid. Iga selline programm kujutab endast ühte taset – kasuta ära selle turvaauk, et teada saada järgmise taseme parool ja jätka järgmise tasemega.
Mu jaoks oli see päris huvitav, sest mõned turva-augud on sellised, mille kohta ma küll teadsin, et selline halb kood võimaldab häkkeril programmi pangestada, aga mitte seda, et ka suvalist koodi jooksutada. Näiteks printf – jah, selle abil on võimalik kurjal häkkeril käivitada mistahes koodi.

Massiivid ja C++

C++ koodis üldiselt ei ole vaja massiive kasutada, aga mõnikord siiski ei pääse neist. Sel juhul ei tahaks väga kirjutada funktsioone nagu C mehed, et massiivi funktsioonile andes antakse paar viidast ja selle taga olevate elementide arvust. C++ õnneks võimaldab seda oluliselt lihtsustada.

// C stiil massiivide edastamiseks
void f(char* p, size_t n) { ... }
 
void g()
{
     char bla[] = "Hello!";
     char* blaa = h();
     f(bla, sizeof(bla)/sizeof(bla[0])); // [1] öäkk
     f(bla, countof(bla)); // [2] parem kui eelmine, aga siiski öäkk
     f(blaa, countof(bla)); // [3] uups, vale viit.
}

Real [1] on probleemiks esiteks see, et bla on vaja kirjutada 3 korda, mis loob suurepärase võimaluse midagi valesti teha, eriti refaktoorimise käigus. Real [2] on sama probleem vaatamata sellele, et seal on ainult 2 korda bla. Seda probleemi ongi näga real [3]. countof‘ist saab lähemalt lugeda siin.

Kuna C++ võimaldab template‘e kasutada, võib tekkida soov teha sellest funktsioonist template:

template <size_t n>
void f(char (&)p[n]) { ... }
 
void g()
{
     char bla[] = "Hello!";
     f(bla);
 
     char* p = bla;
     f(p); // viga!
}

Tüübikindluse koha pealt on parem küll – enam ei ole võimalik anda suvalist viita vaid funktsioonile tuleb kindlasti massiiv anda. Samas on jälle probleemiks see, et funktsiooni definitsioon peab olema päises.

See võib viia mõtteni, et tegelikult võiks see funktsioon ikkagi välja kutsuda selle C-stiilis funktsiooni:

void f(char* p, size_t n) { ... }
 
template <size_t n>
void f(char (&)p[n])
{
    f(p, n);
}
 
void g()
{
     char bla[] = "Hello!";
     f(bla);
}

Sama trikki kasutatakse näiteks Visual C++’ga kaasas oleva C runtime funktsioonides, kui see vastava makroga peale on keeratud.

Parem oleks muidugi teha nii, et seda C stiilis funktsiooni üldse ei oleks ja funktsiooni saaks ikka kasutada selliselt nagu ülalolevat C++ funktsiooni, aga ilma et see funktsioon ise template oleks. Jah, ka see on võimalik.

template <typename T>
class Array
{
public:
    typedef T value_type;
    typedef T& reference;
    typedef T const& const_reference;
    typedef T* pointer;
    typedef T const* const_pointer;
    typedef pointer iterator;
    typedef const_pointer const_iterator;
 
    template <size_t S>
    /* implicit*/ Array(T(&a)[S])
       : array_(a)
       , size_(S)
    { }
 
    reference operator[](size_t idx)
    {
        assert(idx < _size);
        assert(array_ != nullptr);
        return array_[idx];
    }
 
    const_reference operator[](size_t idx) const
    {
        assert(idx < size_);
        assert(array_ != nullptr);
        return array_[idx];
    }
 
    size_t size() const
    {
        return size_;
    }
 
    iterator begin()
    {
        return array_;
    }
 
    iterator end()
    {
        return array_ + size_;
    }
 
    const_iterator begin() const
    {
        return array_;
    }
 
    const_iterator end() const
    {
        return array_ + size_;
    }
 
private:
    pointer const array_;
    size_t const size_;
};
 
void f(Array<char> p) { /* kasuta p'd nagu tavalist massiivi */ }
 
void g()
{
     char bla[] = "Hello!";
     f(bla);
}

Nimi Array võib-olla jätab mulje, et tegelikult see ise ongi massiiv ja on seal sisalduvate elementide omanik – nii see ei ole. Array objekt on lihtsalt vaade massiivile. Selle parandamiseks võiks selle klassi nimeks olla hoopis ArrayView või ArrayAdapter või midagi sellist.
Miks on sellise klassi kasutamine parem kui tavalise massiivi kasutamine?

  • sest see ei konverteeru automaatselt viidaks
  • sest sellise objekti funktsioonidele edastamine on lihtsam kui tavaliste massiivide edastamine
  • sest sellise objekti funktsioonidele edastamine on veakindlam – debug versioonis annab assert märku, kui üritad lugeda või kirjutada väljapoole massiivi piire, massiivi pikkus on õige
  • sest sellises objektis on rohkem infot ilma, et see nõuaks rohkem protsessori tööaega või mälu – massiivi ja selle pikkust edastades liiguvad samad andmed, aga programmeerija peab selleks vaeva nägema
  • ….

Juhuks, kui rakenduses kasutatakse tihti viita ühele objektile kui ühe-elemendilist massiivi, saab seda klassi natuke täiendades ka sellises olukorras kasulikuks teha. Selleks on vaja lihtsalt lisada selline konstruktor:

    ...
    /* implicit */ Array(T& object)
       : array_(&object)
       , size_(1)
    { }
    ...

Kuidas N9 jälle tööle hakkas

Tükk aega vedeles mu N9 niisama riiulis, aeg-ajalt kangisin seal peal olevat Meego Communiti Edition 1.3 (parema sõna puudumisel võiks seda kirjeldada sõnaga ‘pask’). Harmattan’i sain telefoni peale lõpuks ikkagi nii nagu ma alguses proovisin, kuid firmware, mille ma seekord peale panin, oli erinev sellest, mida ma varem sinna flashida tahtsin. Tuleb välja, et üks oluline bitt infot selle kohta, milline firmware selle telefoni peale läheb, on kirjas SIM-kaardi sahtli peal. Sellele numbrile vastavat firmware otsides leidsin, et Norra versioon on see, mida mul vaja on. Kes oleks võinud seda arvata! Õnneks võimaldas NaviFirm+‘ga selle numbri järgi otsida.

Olles selle firmware‘i peale flash‘inud ja telefoni taas Harmattan’isse buutinud, pidin jälle pettuma – kaamera ei tööta. Pärast kaamera rakenduse käivitamist teatab telefon, et kaamerat ei saa kasutada, kui telefon arvutiga ühendatud on. Käes olevat telefoni vaadates ei tundunud küll, et seal mõni juhe küljes oleks olnud. Lähemal uurimisel selgus, et telefon tõepoolest ei ole arvutiga ühendatud. See, et ta USB-ga ühendatud ei olnud, oli üsna ilmselge. Aga ka mitte wifi-ga ega Bluetooth’iga. Probleem oli hoopis see, et /home/user/MyDocs ei olnud külge monteeritud.

Proovides root’na seda külge monteerida, teatab telefon mulle, et ei luba mul seda teha ja tunneb huvi selle vastu kas ma ikka root olen. Möh?

Internetist otsides leidsin, et ma pole ainuke selle probleemiga. Probleem paistis olevat see, et see failisüsteem polnud korralikult lahti monteeritud ja oli riknenud, aga fsck peaks probleemi lahendama. Buutisin kubuntusse, ühendasin telefoni arvutiga, ja ütlesin telefonile, et ta näitaks ennast USB andmekandjana, tegin fsck – tõepoolest oli seal paar probleemi, mille fsck ka kenasti ära parandas.

Tegin telefonile igaks juhuks restardi ja .. tüng. Ikka sama jama. Vaatasin lähemalt /etc/fstab sisu ja leidsin, et Harmattan loodab selles partitsioonis näha vfat failisüsteemi, aga mul oli sinna varasemast Meego installist jäänud ext4. Tegin sellesse partitsiooni uue fat32 failisüsteemi, restartisin telefoni ja oh seda õnne – telefon tundub toimivat nagu alguses. Saab pilti teha, muusikat kuulata, videosid vaadata ja internetist tõmmatud failid mujale kui musta auku salvestada.

Aga ebamugavam on ikka kui Android.

ACTA

Toetage jah ACTA-t, siis saate poest ainult selliseid CD-sid osta:
http://blogs.technet.com/b/markrussinovich/archive/2005/10/31/sony-rootkits-and-digital-rights-management-gone-too-far.aspx

Kas neid sigatsejaid selliste sigaduste eest kohtussegi siis enam kaevata saab?

“Midagi ei muutu”, my ass!

Progemise huumorit

Mis keeles see kirjutatud on?

[](){[](){[](){[](){[](){}();}();}();}();}();

:D

Minimalistlik C ja C++ koodistandard

The power of 10‘ on C ja C++ koodistandard süsteemide, kus ohutusnõetel on süsteemi nõuetes oluline kaal, arendajaile mõeldud. Välja on see töötatud NASA JPL’is. Lühikokkuvõte sellest koodistandardist oleks selline:

  1. Kirjuta lihtsat koodi, ära kasuta goto‘d.
  2. Kõikidel tsüklitel peab olema fikseeritud ülempiir ja seda peab staatilise analüüsi tööriist suutma triviaalselt tõestada.
  3. Ära kasuta dünaamilist mälueradust pärast initsialiseerimist.
  4. Ükski funktsioon ei tohi olla pikem kui ühele paberilehele printida saab.
  5. Assert’ide tihedus peaks olema vähemalt 2 assert’i funktsiooni kohta. Kasutud assert’id nagu näiteks assert(true) ei lähe arvesse.
  6. Objektidele tuleks anda vähim võimalik skoop.
  7. Mitte-void tagastustüübiga funktsioonide tagastatavat väärtust peab alati kontrollima.
  8. Preprotsesori kasutamine peab piirduma #include ja lihtsate makrode kasutamisega.
  9. Viitade (pointer) kasutamist peab piirama. Üle ühe taseme ei tohi viitasid olla (st. viit viidale on lubamatu). Funktsiooniviidad on keelatud.
  10. Koodi peab kompileerima selliselt, et kompilaatori kõik hoiatused loetakse vigadeks ja hoiatuste tase peab olema määratud kõige pedantsemaks, mida kompilaator võimaldab ja selliste seadetega tuleb kompileerida koodi projekti esimesest päevast alates.

Mulle selline koodistandard meeldib. Kui tegemist ei ole ohutuskriitilise projektiga, siis võib sellest nimekirjast välja jätta näiteks dünaamilise mälueralduse keelu ja tsüklite fikseeritud ülempiiri, C puhul ka funktsiooniviitade kasutamise keelu. Ülejäänud reegleid sobib mu meelest kasutada igasugustes projektides. Selline hulk reegleid aga tähendab seda, et vajalik on hea staatilise analüüsi tööriist, mida pidevalt kasutama peaks. Lisaks kompilaatori poolt antavatele hoiatustele tuleks vigadeks lugeda ka hoiatusi, mida staatiline koodianalüüs annab, isegi siis kui tegemist on valepositiivsega. Valepositiivsete hoiatuste vigadeks lugemist põhjendatakse sellega, et alati ei pruugi olla väga lihtsalt tõestatav see, et tegemist ikkagi valepositiivsega on ja isegi kui on tegelikult valepositiivsega tegu, siis peab koodi kirjutama nii lihtsalt, et tööriist sellega hakkama saab.

Exe allkirjastamine ID-kaardiga

Tekkis küsimus – kuidas teha nii, et alla laetud faili käivitamisel ei näidataks dialoogi, kuhu on kirjutatud “unknown publisher” vaid ikka midagi mõistlikumat, näiteks minu nimi. Tuleb välja, et Windows SDK sees on tööriist nimega signtool.exe, millega seda teha saab. Lahe on selle tööriista juures see, et mitte ainult sertifikaadiga vaid ka kiipkaardiga on võimalik signeerida, muuhulgas ka Eesti ID-kaardiga. Katsetamiseks võtsin ette ühe suvalise exe ja proovisin järgmist käsurida:

>signtool sign /a /d "Mingi kirjeldus" bla.exe

Tulemuseks oli dialoog, mis ütles, et kiipkaardilugejat ei leia. Ühendasin kiipkaardilugeja masinaga ja panin oma ID kaardi sisse, mille peale paluti mul PIN2 sisestada ja tadaa – exe oligi signeeritud. Katsetamiseks kopeerisin allkirjastatud exe oma Download kataloogi (sealt käivitades Windows näitab varem mainitud dialoogi) ja käivitasin – enam ei näidatudki mulle “unknown publisher” vaid dialoogis oli publisher‘i koha peal minu nimi koos isikukoodiga.

Kuidas N9 pangestus

Juhtus nii, et Ovi Store’st ühte mängu installima hakates, hakkas MeeGo (tegeliklt see pakettid haldur, mis MeeGo peal on) heast peast kõike telefonis leiduvat kustutama enne mängu installimist – ikoonid kadusid järjest start-screen’ilt. Kõige haigem selle juures oli see, et helistamise funktsioon kadus esimeste seas. Lõpuks jäi paar üksikut rakendust alles, needki minu oma tehtud katsetused. Telefoni restartides on näha ainult NOKIA logo. Kõik.

Ehk on parandatav. Google’ist “flash N9″ otsides jõudsin siia. Sealt edasi siia. Viimaselt lingilt tuleks alla tõmmata flasher. Näiteks WinFlasher_3.12.1.exe, kui oled windowsi kasutaja. MeeGo wiki’s on jutt sellest, kuidas MeeGo Community Edition’i installida. Minul see eriti ei tahtnud õnnestuda. Flashimine jäi poole peal kinni:

>flasher -F moslo-rootfs-1.2011.34-2_RM680-OEM1-916_0.0.13-12.1.bin -f
flasher 3.12.1 (Oct  6 2011) Harmattan
WARNING: This tool is intended for professional use only. Using it may result
in permanently damaging your device or losing the warranty.

Suitable USB interface (bootloader/phonet) not found, waiting...
Found device RM-696, hardware revision 1507
NOLO version 2.1.5
Version of 'sw-release': DFL61_HARMATTAN_20.2011.40-4_PR_009
Sending ape-algo image (7020 kB)...
100% (7020 of 7020 kB, avg. 8988 kB/s)
Suitable USB interface (phonet) not found, waiting...


Selle koha peal peaks asi nüüd edasi minema ja hakkama midagi telefoni kirjutama, aga ei.

Ka MOSLO kirjutamine telefoni ebaõnnestus, sest device lock oli enne seda õnnetust peal. Et device lock maha saada, on vaja telefon tühjendada:

>flasher --erase-user-data=secure



Umbes 20 minutit hiljem oligi mu telefon täitsa tühi. Proovisin uuesti MOSLO’t installida ja oh seda rõõmu – saigi installitud. Nüüd pärast sisselülitamist näitab telefon mõne sekundi hoiatust, et garantiid telefonil enam pole ja muud säärast mula, seejärel paistab lahe oldskool välimusega konsool, mis ütleb, et rootfs on nüüd USB kaudu nähtav. Sinna peaks oma operatsioonisüsteemi kopeerima (Windows-ga pole siit alates enam midagi peale hakata, sest ext4 failisüsteemi draiverit ega muid tööriistu mu teada Windows’is ei ole. XP jaoks on ext3 draiver, aga mitte uuemate windowsite jaoks). Linuxit on vaja. Kuna mul läheduses ühtki Linuxi live CD’d pole, siis installisin esimese essejuhtuva linuxi – Ubuntu, mida ilma partitsioonidega jamamata installida saab.

Siit sai tõmmatud MeeGo 1.3 Community Edition ja MeeGo N9 wiki juhendeid järgides sai Ubuntus külge monteeritud USB kaudu nähtav telefoni rootfs ja alla tõmmatud pakk sinna lahti pakitud. Reboot. Ja mul ongi telefonis nüüd uus OS.

Hea on muidugi see, et mu telefon ei muutunud telliskiviks, kehv on see, et see MeeGo 1.3 CE ei ole N9 vaid N950 jaoks tehtud. N950′l on füüsiline klaviatuur, N9′l aga ainult on/off, vol up/down nupud, mis tähendab, et lihtsalt žestikuleerides ei saa mööda kasutajaliidest ringi käia ja kui rakendus ise ei paku võimalust enda kinni panekuks mõne nähtaval oleva nupuga, ongi kõik – reboot’i vaja teha või siis ssh kaudu telefoni sisse logida ja seal protsessile kill -9 teha. Lisaks kasutajaliidese probleemidele on ka mõned riistvata ühilduvuse probleemid – bluetooth, kaamera, GSM ja tõenäoliselt veel midagi ei toimi. WiFi toimib, pilti näeb, puudutustele reageerib ka.

Väärt lugemist

“Uncle Bob Martin” kirjutab sõltuvuste ümber pööramise põhimõttest (i.k. dependency inversion principle). http://objectmentor.com/resources/articles/dip.pdf. Jutt on seal C++’ist, kuid sisuline osa on rakendatav ka muude keelte puhul.