C-keele massiivide pikkuse määramine: täielik juhend algajatele ja edasijõudnutele

1. Sissejuhatus

Programmeerimiskeel C on oma lihtsuse ja suure jõudluse tõttu laialdaselt kasutusel süsteemiarenduses ja manussüsteemides. Nende hulgas on massiivid oluline andmestruktuur, mis võimaldab andmeid korraga hallata ning mida kasutatakse sageli paljudes programmides.

Selles artiklis selgitame üksikasjalikult, kuidas C-keeles massiivi pikkust (elementide arvu) määrata. Keskendume eelkõige algajatele raskusi valmistavatele punktidele ning anname samm-sammulised juhised alates põhitõdedest kuni rakenduseni, et omandada oskus massiivi pikkust täpselt hinnata.

2. Massiivide põhimõisted

Mis on massiiv?

Massiiv on andmestruktuur, mis võimaldab hallata sama andmetüüpi väärtusi üheskoos. See on kasulik, kui on vaja töödelda korraga mitut andmeühikut, ning eraldab sama tüüpi andmetele järjestikuse mäluruumi.

Massiivide kasutusviisid

  1. Andmete hulgitöötlus – sobib juhtudel, kui on vaja hallata sama tüüpi andmeid, näiteks õpilaste hindeid või andureidelt saadud mõõtmisi.
  2. Korduvate protsesside teostamine – tänu järjestikusele juurdepääsule tsüklites on võimalik sama töötlust efektiivselt korrata.
  3. Mälu haldamine – mäluruumi järjestikune kasutamine võimaldab kiiremat ja tõhusamat juurdepääsu.

Massiivi tööpõhimõte

Massiivi elementidele pääseb ligi indeksite abil. C-keeles algab indeks 0-st ning viimane element on juurdepääsetav väärtusega massiivi suurus - 1.

Näide:

int numbers[5] = {10, 20, 30, 40, 50};
printf("%d\n", numbers[0]); // kuvab 10
printf("%d\n", numbers[4]); // kuvab 50

Selles näites sisaldab massiiv numbers viis täisarvu ning igale elemendile saab ligi vastava indeksi abil.

侍エンジニア塾

3. Massiivide deklareerimine ja initsialiseerimine

Kuidas massiivi deklareerida

C-keeles deklareeritakse massiiv järgmiselt:

tüüp massiivi_nimi[suurus];

Konkreetne näide:

int scores[10]; // täisarvutüüpi massiiv 10 elemendiga

Selles näites luuakse täisarvudest (int) koosnev massiiv scores ning eraldatakse mälu 10 elemendi jaoks.

Massiivide initsialiseerimine

Massiivi saab initsialiseerida kohe deklareerimise hetkel.

  1. Näide selgest initsialiseerimisest
int values[5] = {1, 2, 3, 4, 5};
  1. Osaline initsialiseerimine
int data[5] = {10, 20}; // ülejäänud elemendid saavad väärtuseks 0
  1. Suuruse väljajätmine initsialiseerimisel
int numbers[] = {10, 20, 30}; // elementide arv arvutatakse automaatselt (3)

Mitteinitsialiseeritud massiivid

Kui massiivi ei initsialiseerita, võivad selles olla määramata väärtused (nn “prügiandmed”). Seetõttu on soovitatav vajadusel massiiv alati initsialiseerida.

4. Massiivi pikkuse (elementide arvu) määramine

C-keeles on massiivi täpne pikkus oluline, eriti tsüklite ja andmete haldamisel. Selles jaotises selgitame täpselt, kuidas pikkust määrata ja millele tähelepanu pöörata.

4.1 Pikkuse määramine sizeof operaatori abil

Levinum viis massiivi pikkuse määramiseks on kasutada sizeof operaatorit, mis tagastab muutuja või tüübi suuruse baitides.

Põhinäide:

#include <stdio.h>

int main() {
    int array[5] = {10, 20, 30, 40, 50};
    int length = sizeof(array) / sizeof(array[0]); // arvutab elementide arvu

    printf("Massiivi pikkus: %d\n", length); // väljund: 5
    return 0;
}

Olulised punktid:

  • sizeof(array) – kogu massiivi suurus baitides.
  • sizeof(array[0]) – ühe elemendi suurus baitides.
  • Kogu suurus jagatakse ühe elemendi suurusega, et saada elementide arv.

4.2 Massiivi edastamine funktsioonile

Kui massiiv edastatakse funktsioonile, teisendatakse see pointeriks. Sel juhul tagastab sizeof pointeri suuruse (tavaliselt 4 või 8 baiti) ega anna õiget pikkust.

Probleemne näide:

#include <stdio.h>

void printArrayLength(int arr[]) {
    printf("Suurus: %ld\n", sizeof(arr) / sizeof(arr[0])); // ei tööta õigesti
}

int main() {
    int array[5] = {1, 2, 3, 4, 5};
    printArrayLength(array);
    return 0;
}

Lahendus: edastada pikkus eraldi argumendina.

#include <stdio.h>

void printArrayLength(int arr[], int length) {
    printf("Massiivi pikkus: %d\n", length);
}

int main() {
    int array[5] = {1, 2, 3, 4, 5};
    int length = sizeof(array) / sizeof(array[0]);
    printArrayLength(array, length);
    return 0;
}

5. Sõnemassiivi pikkuse määramine

C-keeles käsitletakse sõnesid samuti massiividena. Kuid erinevalt tavalistest täisarvu massiividest on sõned char-tüüpi massiivid, mille lõppu lisatakse erimärk '\0' (nullmärk). Selles jaotises selgitame, kuidas määrata sõnemassiivi pikkust ja millele tähelepanu pöörata.

5.1 Sõne ja massiivi seos

Sõne on char-tüüpi märkide kogum. Näide:

char str[] = "Hello";

See kood salvestab massiivi str mällu järgmiselt:

Hello‘\0’

Olulised punktid:

  • '\0' tähistab sõne lõppu.
  • Massiivi suurus on 6 baiti, kuna nullmärk on kaasatud.

5.2 Pikkuse määramine funktsiooniga strlen

Sõne tegeliku pikkuse (märkide arvu) määramiseks kasutatakse standardteegi funktsiooni strlen.

Näide:

#include <stdio.h>
#include <string.h>

int main() {
    char str[] = "Hello";
    printf("Sõne pikkus: %ld\n", strlen(str)); // väljund: 5
    return 0;
}

Märkus:

  • strlen loendab ainult märgid, jättes nullmärgi arvestamata.

5.3 Erinevus sizeof ja strlen vahel

Pikkust saab määrata ka sizeof-iga, kuid tulemused erinevad.

Näide:

#include <stdio.h>
#include <string.h>

int main() {
    char str[] = "Hello";
    printf("sizeof: %ld\n", sizeof(str));  // väljund: 6 (sh '\0')
    printf("strlen: %ld\n", strlen(str)); // väljund: 5
    return 0;
}

Peamised erinevused:

  • sizeof tagastab kogu massiivi suuruse baitides (sh nullmärk).
  • strlen tagastab ainult märkide arvu (nullmärki arvestamata).

6. Muutuvpikkusega massiivide (VLA) kasutamine

C99 standardist alates on võimalik kasutada muutuvpikkusega massiive (VLA – Variable Length Array). See võimaldab massiivi suuruse määrata programmi käivitamise ajal, mitte ainult kompileerimise ajal. Siin selgitame VLA omadusi, kasutust ja ettevaatusabinõusid.

6.1 Mis on muutuvpikkusega massiiv?

Tavaline massiiv on staatiline – selle suurus määratakse kompileerimise ajal. VLA on dünaamiline massiiv, mille suurus saab sõltuda kasutaja sisendist või arvutustulemusest.

Staatiline massiiv:

int arr[10]; // suurus määratud kompileerimise ajal

VLA näide:

int size;
scanf("%d", &size); // suurus määratakse käitusajal
int arr[size];      // massiiv luuakse jooksvalt

6.2 VLA põhiline kasutamine

Näide:

#include <stdio.h>

int main() {
    int n;

    printf("Sisesta massiivi suurus: ");
    scanf("%d", &n);

    int arr[n]; // VLA deklaratsioon

    for (int i = 0; i < n; i++) {
        arr[i] = i + 1;
    }

    printf("Massiivi elemendid: ");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");

    return 0;
}

Väljundinäide:

Sisesta massiivi suurus: 5
Massiivi elemendid: 1 2 3 4 5
  • VLA võimaldab määrata suuruse alles käitusajal.
  • See teeb andmete haldamise paindlikumaks.

7. Massiivi pikkusega seotud ettevaatusabinõud

C-keeles on massiivide puhul suuruse (pikkuse) korrektne haldamine äärmiselt oluline. Kui pikkus määratakse valesti, võib see põhjustada programmi vigast käitumist või tõsiseid turvariske. Selles jaotises käsitleme massiivi pikkusega seotud ohte ja ohutuma koodi kirjutamise soovitusi.

7.1 Massiivi piiridest väljapoole pääsemise vältimine

Kuna massiiv on fikseeritud suurusega, põhjustab sellele ettenähtud piiridest väljapoole juurdepääs ettearvamatuid tulemusi või programmi krahhi.

Vale näide:

#include <stdio.h>

int main() {
    int arr[5] = {1, 2, 3, 4, 5};

    for (int i = 0; i <= 5; i++) { // tingimus on vale
        printf("%d\n", arr[i]);  // väljaspool piire
    }

    return 0;
}

Lahendus:

  • Korrektne tsükli tingimus
for (int i = 0; i < 5; i++) { // õige tingimus
    printf("%d\n", arr[i]);
}
  • Pikkuse dünaamiline arvutamine
int length = sizeof(arr) / sizeof(arr[0]);
for (int i = 0; i < length; i++) {
    printf("%d\n", arr[i]);
}

7.2 Puhvri ületäitumise (Buffer Overflow) oht

Üks levinumaid probleeme massiividega on puhvri ületäitumine, mis tekib siis, kui kirjutatakse andmeid massiivi piiridest väljapoole ja kirjutatakse üle teisi mälupiirkondi.

Ohtlik näide:

#include <stdio.h>
#include <string.h>

int main() {
    char buffer[10]; // 10 baiti
    strcpy(buffer, "This string is too long!"); // ületab massiivi piiri
    printf("%s\n", buffer);
    return 0;
}

Lahendus:

  • Turvalisemad funktsioonid
strncpy(buffer, "This string is too long!", sizeof(buffer) - 1);
buffer[sizeof(buffer) - 1] = '\0'; // tagab nullmärgi lõpus

8. Kokkuvõte

Selles artiklis käsitlesime C-keele massiivide pikkusega seotud teemasid alates põhitõdedest kuni praktiliste näideteni. Massiiv on võimas andmestruktuur, kuid vale kasutamine võib põhjustada programmeerimisvigu ja turvaauke. Järgime artikli peamisi punkte:

8.1 Artikli kokkuvõte

  1. Massiivi põhimõisted ja deklareerimine/initsialiseerimine
  • Massiiv salvestab sama tüüpi elemente järjestikku ja selle suurus määratakse deklareerimisel.
  • Initsialiseerimisel võib suuruse jätta märkimata, kui väärtused on teada.
  1. Pikkuse määramine
  • Staatiliste massiivide puhul kasutada sizeof-i.
  • Funktsioonile edastamisel lisada pikkus eraldi argumendina.
  1. Sõnemassiivid
  • Kasutada strlen-i tegeliku pikkuse jaoks ja mõista erinevust sizeof-iga.
  1. Muutuvpikkusega massiivid (VLA)
  • Võimaldavad suurust määrata käitusajal, kuid vajavad tähelepanu mälukasutuse ja ühilduvuse osas.
  1. Turvaline programmeerimine
  • Vältida piiridest väljumist ja puhvriohtusid, kasutada turvalisi funktsioone.

8.2 Edasised sammud

  1. Koodi praktiline testimine
  • Kompileeri ja käivita artiklis toodud näited.
  1. Edasijõudnute harjutused
  • Katseta mitmemõõtmelisi massiive ja pointeritega seotud lahendusi.
  1. Turvalise koodi harjumus
  • Planeeri koodi kirjutades alati ka turvalisus ja vigade käsitlemine.

8.3 Lõppsõna

Massiiv on C-keele aluspõhimõiste, mis on samaaegselt nii lihtne kui ka mitmekülgne. Kui järgid artiklis toodud juhiseid, saad kirjutada turvalist ja efektiivset koodi.

Korduma kippuvad küsimused (KKK)

K1: Miks ei tööta sizeof funktsiooni sees massiivi pikkuse leidmisel õigesti?

V:
sizeof tagastab massiivi suuruse ainult siis, kui massiiv on deklareeritud samas ulatuses (scope). Kui massiiv edastatakse funktsioonile, teisendatakse see viidaks (pointer). Pointer sisaldab ainult massiivi algusaadressi, mistõttu sizeof tagastab pointeri suuruse (tavaliselt 4 või 8 baiti), mitte tegeliku elementide arvu.

Lahendus:
Edasta massiivi pikkus eraldi argumendina funktsioonile.

void printArray(int arr[], int size) {
    for (int i = 0; i < size; i++) {
        printf("%d\n", arr[i]);
    }
}

int main() {
    int array[5] = {1, 2, 3, 4, 5};
    printArray(array, sizeof(array) / sizeof(array[0])); // pikkus edastatakse
    return 0;
}

K2: Kumb on parem sõne pikkuse leidmiseks, sizeof või strlen?

V:
Sõltub eesmärgist.

  • sizeof: tagastab kogu massiivi suuruse baitides (sh '\0'). Sobib puhvri suuruse kontrollimiseks.
  • strlen: tagastab tegeliku märgijada pikkuse (ilma '\0'-ta). Sobib sõne pikkuse määramiseks.

Näide:

char str[] = "Hello";
printf("%ld\n", sizeof(str));  // väljund: 6 ('\0' kaasas)
printf("%ld\n", strlen(str)); // väljund: 5

K3: Kumb valida, muutuvpikkusega massiiv (VLA) või malloc-iga dünaamiline massiiv?

V:
Olenevalt olukorrast.

  • VLA eelised:
    • Lihtne kasutada, suurus määratakse käitusajal.
    • Kasutab virnamälu (stack), mis võib olla piiratud suurusega.
    • Mõnes keskkonnas (nt C11 ja uuemad) võib VLA tugi puududa.
  • malloc eelised:
    • Kasutab hunnikumälu (heap), mis sobib suurte andmehulkade jaoks.
    • Nõuab mäluhaldusega (mälu vabastamine free()-ga) tegelemist, kuid pakub suuremat paindlikkust ja ühilduvust.

K4: Mis juhtub, kui unustan malloc-iga eraldatud mälu vabastada?

V:
See põhjustab mäluleke (memory leak). Mälulekke kuhjumine võib aeglustada süsteemi või põhjustada programmi krahhi.

Lahendus:
Pärast kasutamist vabasta alati mälu free()-ga.

int *arr = malloc(10 * sizeof(int));
if (arr == NULL) {
    printf("Mälu eraldamine ebaõnnestus\n");
    return 1;
}

// ... kasuta massiivi ...

free(arr); // vabastab mälu

K5: Kuidas vältida puhvri ületäitumist?

V:
Puhvri ületäitumine tekib siis, kui kirjutatakse massiivi piiridest väljapoole. See võib põhjustada turvariske ja programmi krahhi. Ennetamiseks:

  1. Kontrolli alati massiivi suurust sisendi töötlemisel.
  2. Kasuta turvalisi funktsioone – näiteks strncpy strcpy asemel, snprintf sprintf asemel.
  3. Jäta puhvris alati ruumi nullmärgile.
  4. Testi koodi äärmuslike sisenditega.

Kokkuvõte

See KKK aitas vastata levinud küsimustele massiivide pikkuse leidmise ja ohutu kasutamise kohta. Koos artikli põhiosaga moodustab see tervikliku juhendi turvalise ja tõhusa C-keele massiivide käsitlemise kohta.