Archive for August, 2008

Broken

Friday, August 22nd, 2008

Tā es Sāremā paliku bez telefona…

Code safety

Tuesday, August 12th, 2008

Pēdējā laikā koda pārskatos (code review) plosās NULL references dēmoni, jo, redz’, C++ funkcijai ir iespējams nodot NULL referenci un visām funkcijām šādi gadījumi jāapstrādā.

void bar(int &foo)
{
    if (&foo == NULL) {
        std::cerr << "foo == NULL" << std::endl;
    }
}
int main()
{
    int *foo = NULL;
    bar(*foo);
    return 0;
}

C++ standarts gan saka, ka NULL references nevar eksistēt, tādēļ problēma ir nevis funkcijā, bet izsaucošajā kodā, kas veic NULL rādītāja dereferenci (*foo) un uzprasās uz problēmām:

Note: in particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the "object" obtained by dereferencing a null pointer, which causes undefined behavior.

Bet nu ej un kādam to iestāsti... Interesanti, ka programma neavarē pie NULL rādītāja dereferences kā tas notiek citos gadījumos, bet turpina strādāt. Tā nu ir daļa no kompilatora "undefined behaviour". No kompilatora viedokļa references ir tie paši rādītāji, kuriem aizliegtas aritmētiskas operācijas un vēl pāris lietas. Tāpēc rādītāja dereference patiesībā nenotiek un uz funkciju tiek nodota rādītāja vērtības kopija.

No tās pašas operas ir iespēja izsaukt NULL objekta ne-virtuālas metodes un avarēt izmantojot kādu atribūtu.

class Foo {
public:
    void bar() {
        if (this == NULL) {
            std::cerr << "this == NULL" << std::endl;
        }
    }
};
int main()
{
    Foo *foo = NULL;
    foo->bar();
    return 0;
}

Tas iespējams tādēļ, ka ne-virtuālas metodes darbojas tāpat kā funkcijas, tikai kompilators kā pirmo parametru pieliek rādītāju uz objektu (this). Virtuālām metodēm ir līdzīgi, tikai rādītājs uz objektu tiek izmantots, lai atrastu rādītāju uz funkciju, tāpēc programma avarē vēl pirms metodes izsaukšanas.

Lietainajā atvaļinājuma bezdarbības laikā šķiroju savu elektronisko bibliotēku un uzdūros C++ guru Herb Sutter stāstam, kas šajā gadījumā ir kā naglai pa galvu:

Just as you must assume that a non-null pointer is valid, you must assume that a reference is valid. You must have faith in your fellow programmers.

Tātad "sliktajiem" jeb "kā C++ ļauj iešaut sev kājā" koda piemēriem, var pievienot arī jebkuras adreses, kas nenorāda uz objektu (0xdeadbeaf, piemēram) izmantošanu un objektus, kuriem jau ir izsaukti destruktori. Š ajā piemērā kompilators gan brīdina, ka tiek atgriests lokālais mainīgais, bet parasti kļūdas nav tik triviālas un kompilatoram pamanāmas.

class Foo {
public:
    Foo() : x(0) {}
    ~Foo() { x = 666; }
    virtual void bar() {
        std::cerr << x << std::endl;
    }
private:
    int x;
};
Foo * factory()
{
    Foo foo;
    return &foo;
}
int main()
{
    Foo *foo = factory();
    foo->bar();
    return 0;
}

Mans subjektīvais viedoklis ir tāds, ka sliktas ne-NULL adreses ir daudz, daudz bīstamākas par NULL. Ja pēdējās novedīs pie programmas avārijas un normāla starpprogrammatūra (middleware) nodrošinās pieejamību ar replicēšanu un automātisku pārstartēšanu, tad pirmās var ļaut programmai strādāt ar neprognozējamu rezultātu.

Programmējot valodās, kas ļauj iešaut sev kājā, ir kaut kādi pamati, uz kā balstās visas programmas. Simbolu virknes beidzas ar 0 - ja tas netiek ievērots, pārmetumi, ka funkcija "lien" aiz virknes robežām, ir lieki. Ja masīvā mēģina ierakstīt vairāk datus nekā vietas tajā - tiks pārrakstīti kaimiņi atmiņā un memcpy() nav tajā vainojama. Ja funkcijai nodod mazāk parametrus nekā tā sagaida (tipiska kļūda darbā ar funkciju rādītājiem vai printf funkciju saimi) - tiks izmantota draza no steka. Pamatos ir arī paļaušanās, ka jebkurš ne-NULL rādītājs ir labs un visas references rāda uz eksistējošiem objektiem, jo pārliecināties par to ir neiespējami (vai gandrīz neiespējami). Programmas vajag būvēt uz šiem pamatiem, nevis katru ķieģeli iekārināt gaisā, lai tas turētos savā vietā arī bez pamatiem.

Noslēgumam vēl kāds padoms no Guru:

Beware the path to Undefined Behavior. Once you start down that path, it will dominate your life forever.