Z Javy migrujuvší céčkar 5: … v tym poľu

Dajme si ďalšiu z nepraktických úloh v Cčku, ktorá ukáže rozdiely medzi Javou a týmto jazykom.

Janko má zo slovenčiny tieto známky: 1, 1, 5, 2, 3, 2. Vypíšte jeho známky pod seba.

Hej, je to stupidita, ale ukážeme, koľko radosti, strasti a vedľajších problémov pri tom môže vyskákať.

Pre jednoduchosť (aby sme sa nezaprplali vstupno-výstupnými operáciami) budeme predpokladať, že známky sú zadrôtované v súbore.

Implementácia v Jave:

public class Priemer {
    public static void main(String[] args) {
        int[] hodnoty = { 1, 1, 5, 2, 3, 2 };
        for(int hodnota : hodnoty) {
            System.out.println(hodnota);
        }
    }
}

Ako by to vyzeralo v C?

… v tym poľu

Povedzme si niečo o poliach: rozdielov v syntaxi a význame je dosť veľa.

Neopovážte sa robiť toto:

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int[] hodnoty = { 1, 1, 5, 2, 3, 2 };

    int i;
    for(i = 0; i < hodnoty.length; i++) {
        printf("%d\n", hodnoty[i]);
    }
}

Toto fungovať nebude z dvoch dôvodov. Predovšetkým, syntax pre deklaráciu polí nepozná javácku syntax:

int[] hodnoty = { 1, 1, 5, 2, 3, 2 };

Hláška kompilátora je kúzelná:

error: Expected token ";", found "{"

Správna verzia je takto:

int hodnoty[] = { 1, 1, 5, 2, 3, 2 };

Áno, zátvorky sú až za názvom premennej. Napodiv, Java podporuje oba zápisy (lebo chceli uľahčiť migráciu z C), i keď sa s Cčkarskym takmer nestretnete.

Ďalší priekak je s cyklom. Naše hrôzostrašné hodnoty.length fungovať nebude ani omylom:

error: request for member 'length' in something not a structure in union.

Ako môžem zistiť dĺžku poľa?

Jednoduchá odpoveď: nemôžete.

Odhmlite zraky: existujú spôsoby, ako to dosiahnuť (viď nižšie), ale okolnosti sú pomerne obskúrne: prakticky len vtedy, ak je pole deklarované ako globálna či lokálna premenné a počet prvkov viete už v čase písania programu.

C totiž implementuje polia veľmi jednoducho: je to len krajší zápis pre viacero po sebe idúcich chlievikov v pamäti:

-------------------------
| 1 | 1 | 5 | 2 | 3 | 2 |
-------------------------
 int int int int int int

Nikde sa neeviduje, koľko tých chlievikov ste využili: zodpovednosť za evidenciu je na vás. Áno, C ide po výkonnosti a nie po pohodlnosti. Ak chcete používať nejaký fintivý trik, musíte si ho vyrobiť sami.

OK, urobme ďalší pokus:

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int pocet_hodnot = 6;
    int hodnoty[] = { 1, 1, 5, 2, 3, 2 };

    int i;
    for(i = 0; i < pocet_hodnot; i++) {
        printf("%d\n", hodnoty[i]);
    }

    return EXIT_SUCCESS;
}

Toto už bude fungovať ako chceme.

Ale predstavme si takúto situáciu, keď úbohý vývojar o polnoci netrafil počet prvkov.

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int pocet_hodnot = 7;
    int hodnoty[] = { 1, 1, 5, 2, 3, 2 };

    int i;
    for(i = 0; i < pocet_hodnot; i++) {
        printf("%d\n", hodnoty[i]);
    }

    return EXIT_SUCCESS;
}

Opakujem: všimnite si, že pocet_hodnot je sedem, ale skutočný počet prvkov je len šesť. Program sa radostne skompiluje, a dokonca aj zbehne! (Toto je typická chyba zvaná „plusmínus 1”, kde sa vývojári pomýlia o jeden prvok hore-dole.)

Výpis bude kúzelný:

1
1
1
5
2
3
2
0

Alebo:

1
1
1
5
2
3
2
12386051

Alebo:

1
1
1
5
2
3
2
7522

Alebo:

* [tu vložte screenshot hlásenia operačného systému o padnutom programe] *

Základný rys Cčka je (aby sme citovali legendu učebníc C pána Herouta):

Jazyk C zásadně nekontroluje meze polí!

Opäť je to kvôli efektivite. Pole je nič iné, než označená postupnosť chlievikov v pamäti a je na vás, aby ste vedeli, koľko chlievikov (= prvkov poľa) ste si alokovali. Ak vyleziete spoza hraníc poľa, dostanete sa do územia, ktoré vám nepatrí. Možno patrí susednému procesu, možno operačnému systému, ale rozhodne nie je je vaše. A čo nie je vaše, na to sa nedá spoliehať.

Hodnota 0 alebo 12386051 alebo 7522 je náhodná hodnota, ktorá sa práve nachádza v pamäti mimo hraníc, ktoré boli alokované.

Dávajte si na to mimoriadny pozor!

V prípade, že zapisujete do cudzej pamäte, je to rovnako zlé: akurát niekedy máte väčšiu šancu na priamy pád programu.

    int hodnoty[] = { 1, 2 };
    hodnoty[128] = 3;

Mnoho operačných systémov dovolí čítať z cudzej pamäte, ale zápis vedie rovno k pádu — jednoducho nie je možné zapisovať do pamäte patriacej inému procesu. Na druhej strane sa však môže stať, že pri zápise sa trafíte do pamäte, ktorá prináleží vášmu programu, a začnete si prepisovať obsahy iných premenných, čo je nepredvídateľná katastrofa.

Alternatívna deklarácia polí

Inak môžeme pole deklarovať aj takto:

int hodnoty[2];

hodnoty[0] = 1;
hodnoty[1] = 1;

Inak povedané, rovno pri deklarácii povieme, koľko prvkov bude pole obsahovať. Nezabudnime, že na začiatku sú v poli nedefinované hodnoty!

Tu ale drieme ďalší problém: povedzme, že chceme prechádzať takéto pole. Zbúchame prvú verziu, ktorá funguje, aj keď …

int hodnoty[2];

hodnoty[0] = 1; hodnoty[1] = 1;
int i;
for(i = 0; i < 2; i++) {
    printf("%d\n", hodnoty[i]);     
}

Tá dvojka vyzerá dosť divne: rozhodne nie je dobré, keď sú v cykle „magické čísla“, lebo máme tendencie na ne zabudnúť a potom sa diviť.

Čo takto…

int pocet_hodnot = 2;
int hodnoty[pocet_hodnot];

Ďalej ani nepokračujeme:

error: ISO C90 forbids variable length array 'hodnoty'

Beda! Premennú nemožno použiť ako hodnotu pre počet prvkov! (Aspoň nie v štandarde C89/90).Veľkosť poľa musí totiž byť známa v čase kompilácie programu — a priradenie do premennej je vec, ktorá sa deje až v čase behu. Samozrejme, že kompilátor by mohol byť bystrý a predvídavý… ale toto je C: bystrosť a predvídavosť sú ponechané na vývojara.

Konštanty, teda niečo ako ony

Ako vyriešiť problém, kde mám premennú s hodnotou, ale chcem, aby jej hodnota bola známa už v čase kompilácie?

Viem použiť direktívy preprocesora. Toto je na samostatný článok, ale v stručnosti: „konštantu“ viem zadeklarovať takto:

#include <stdio.h>
#include <stdlib.h>

#define POCET_HODNOT 2
int main(void)
{
    /* ... */
}

Deklarácia

#define POCET_HODNOT 2

povie, že zavádzame konštantu POCET_HODNOT (konvencia káže veľkými písmenami) s hodnotou 2. Od tejto chvíle ju môžeme radostne používať v zvyšku programu podobne, ako bežnú premennú.

Pozor, medzi názvom premennej a hodnotou nie je =! Zavádzanie konštánt sa totiž deje v inom programovacom jazyku ;-). Je to ako keby ste miešali dva jazyky: jazyk C a jazyk preprocesora.

#include <stdio.h>
#include <stdlib.h>

#define POCET_HODNOT 2
int main(void)
{
    int hodnoty[POCET_HODNOT];
    int i;

    hodnoty[0] = 1; hodnoty[1] = 3;


    for(i = 0; i < POCET_HODNOT; i++) {
        printf("%d\n", hodnoty[i]);
    }

    return EXIT_SUCCESS;
}

Nabudúce o poliach a funkciách!

Pridaj komentár

Vaša e-mailová adresa nebude zverejnená. Vyžadované polia sú označené *