Kihagyás

6. gyakorlat

Téma: Memória és pointerek (part 1)

Memória

  • Minden program a memóriában van futás közben
    • Mindene: változók, függvények stb.
  • A memóriának minden byte-ja rendelkezik címmel, azaz a memóriában elfoglalt hellyel

Mi az a pointer?

  • Pointer (vagy pointerváltozó) = mutató (vagy mutatóváltozó)
  • A memória közvetlen elérését teszi lehetővé
  • C-ben és C++-ban eszköz a memória kezelésre
  • Pointerváltozó: Egy memóriacímet jelentő változó
    • Egy pointerváltozónak is van memóriacíme

Memóriakezelő operátorok

Név Operátor Funkció
Címfeloldás * A pointerváltozó által mutatott memória olvasható és írható közvetlenül. Azaz visszaadja a mutatott változó értékét
Cím lekérés & Lekéri a memóriacímet

Pointerhez általában használt % karakter: %lu

Példa

#include <stdio.h>

int main()
{
    int a = 19;
    printf("Memóriacím: %lu\n", &a);

    return 0;
}

Kimenet pl:

Memóriacím: 140727319106932

Néhány futtatás után látható, hogy a változó mindig más memóriacímre kerül

Pointerváltozó deklarálása, definiálása és értékadás

  • A pointer típusa meg kell, hogy egyezzen annak a változónak a típusával, amire mutat
  • A deklarációban lévő * NEM címfeloldó operátor!
  • A sizeof() függvénnyel lekérhető a pointer mérete
  • Megjegyzés: Bármennyi white space lehet az utasítások között
    • Ezért az alábbi példák megegyeznek

Általános szintax:

típus *név = &változó;

típus*név = &változó;

típus* név = &változó;

Példa

#include <stdio.h>

int main()
{
    int a = 19;
    printf("Memóriacím: %lu\n", &a);

    // a 'p' egy pointerváltozó, amely az 'a' változó memóriacímét tárolja
    int *p = &a;
    printf("Memóriacím: %lu\n", p);

    // címfeloldás
    printf("Címfeloldás: %d\n", *p);

    // pointerváltozó mérete
    printf("Pointerváltozó mérete: %ld\n", sizeof(p));

    return 0;
}

Kimenet:

Memóriacím: 140737334081596
Memóriacím: 140737334081596
Címfeloldás: 19
Pointerváltozó mérete: 8

Pointer default értéke

  • Pointernek mindig adjunk értéket!
  • Ha nincs inicializálva vagy az értéke 0, akkor feloldásnál instant segfaultot kapunk
    • Ilyenkor nem létező memóriacímen vagy memóriaszeméttel dolgoznánk

Pointer pointere

  • A pointerváltozó is el van tárolva a memóriában => lekérhető a memóriacíme
  • Egy pointer címét is el tudjuk tárolni egy pointerben => pointerre mutató pointer
  • Lehetséges a végtelenségig, de már a 3-szoros pointernek is kevés gyakorlati gyakorlati van
  • A típusnak itt is meg kell egyeznie a mutatott változó típusával

Deklaráció, definíció, értékadás általános szintaxa:

típus ** név = &(másik_mutató);

Címlekérés példa

#include <stdio.h>

int main()
{
    int a = 19;
    printf("'a' memóriacíme: %lu\n", &a);

    int *p = &a;
    printf("'p' értéke azaz 'a' memóriacíme: %lu\n", p);

    int **p2 = &p;
    printf("'p' memóriacíme: %lu\n", &p);
    printf("Azaz 'p2' értéke: %lu\n", p2);

    return 0;
}

Kimenet:

'a' memóriacímw: 140722784218724
'p' értéke azaz 'a' memóriacím: 140722784218724
'p' memóriacíme: 140722784218728
Azaz 'p2' értéke: 140722784218728

Címfeloldás példa

(Visszajutunk vele az 'a' értékéhez)

#include <stdio.h>

int main()
{
    int a = 19;
    printf("a = %d\n", a);

    int *p = &a;
    printf("*p = %d\n", *p);

    int **p2 = &p;
    printf("**p2 = %d\n", **p2);

    return 0;
}

Kimenet:

a = 19
*p = 19
**p2 = 19

Műveletek pointerekkel

  • A pointerek unsigned long objektumok
  • Korlátozott műveleteket végezhetünk velük

Aritmetikai műveletek

  • Hozzádni nem negatív számot
  • Kivonni nem negatív számot
  • Inkrementálni
  • Dekrementálni

Nem biztos, hogy ki tudjuk írni a 'b' változót, mert nem garantált, hogy a két változó egymás mellé kerül a memóriában

Példa:

#include <stdio.h>

int main()
{
    int a = 42;
    int b = 137;

    int *p = &a;
    printf("'a' memóriacíme: %lu\n", p);
    printf("a = %d\n", *p);

    ++p;

    printf("Inkrementált p: %lu\n", p);
    printf("Reméljük, hogy 'b' = %d\n", *p);

    return 0;
}

Kimenet:

'a' memóriacíme: 140721661103448
a = 42
Inkrementált p: 140721661103452
Reméljük, hogy 'b' = 137

Logikai műveletek

  • A memóriacímek értékeit össze tudjuk hasonlítani a logikai műveletekkel
  • (Megjegyzés: majd fontos lesz a dinamikus memória kezelésnél)

Példa:

#include <stdio.h>

int main()
{
    int a = 1;
    int b = 2;

    int *pa = &a;
    int *pb = &b;

    if(pb > pa)
    {
        printf("Az 'a' változó kisebb memóriacímre került\n");
    }
    else
    {
        printf("A 'b' változó kisebb memóriacímre került\n");
    }

    return 0;
}

Tömbök és pointerek

  • Tömb: A memóriában folytonosan lefoglalt memóriablokkok
  • Egy mutatóval végig lehet haladni ezen a memóriaterületen
    • Biztos, hogy a tömbhöz tartozó memórián fogunk végighaladni

Tömb első elemére mutató pointer

#include <stdio.h>

int main()
{
    char charTomb[5] = { 'a', 'b', 'c', 'd', 'e' };

    char *p1 = charTomb;
    printf("%c\n", *p1);

    char *p2 = &charTomb[0];
    printf("%c\n", *p2);

    return 0;
}

Tömb harmadik elemére mutató pointer

#include <stdio.h>

int main()
{
    char charTomb[5] = { 'a', 'b', 'c', 'd', 'e' };

    char *p1 = &charTomb[3];
    printf("%c\n", *p1);

    char *p2 = charTomb + 3;
    printf("%c\n", *p2);

    return 0;
}

Egy tömb elemeinek kiíratása pointerekkel

#include <stdio.h>

int main()
{
    char charTomb[5] = { 'a', 'b', 'c', 'd', 'e' };

    // Módszer 1
    for (int i = 0; i < 5; i++)
    {
        char *p1 = charTomb + i;
        printf("%c\n", *p1);
    }

    // Módszer 2
    for (int i = 0; i < 5; i++)
    {
        char *p2 = &charTomb[i];
        printf("%c\n", *p2);
    }

    return 0;
}

Teljes char tömb kiíratása

  • Itt gyakorlatilag egy a tömb 0. elemére mutató fel nem oldott pointert adunk át
  • Ilyenkor addig megy, amíg meg nem találja a \0-t => azaz kiírja a teljes tömböt
#include <stdio.h>

int main()
{
    char charTomb[5] = { 'a', 'b', 'c', 'd', 'e' };

    // 1
    char *p1 = charTomb;
    printf("%s\n", p1);

    // 2
    printf("%s\n", charTomb);

    return 0;
}