İçeriğe geç
academia.sh

Ders 01 / 15

İşaretçi Temelleri

Bir bellek bloğunu kimin sahiplendiği sorusu; beş dosyalık ölçüm çekirdeğinin tam koşumu ve dört sınıfın (dil, çağıran, çağrılan, sahipsiz) 4/4/3/2 dağılımı, ardından adres alma ve dolaylama işleçlerinin adres basılmadan ölçülmesi.

Yayın Güncelleme

Bu derste

  • Bir bellek bloğunun sahibini dil, çağıran, çağrılan ve sahipsiz olarak dört sınıfa ayırır
  • Adres alma ve dolaylama işleçlerini adres basmadan eşitlik ve işaret edilen değerle ölçer
  • Bir işaretçinin tipinin, gösterdiği bloğun sahibini söylemediğini örnekle gösterir
İçindekiler

C Temelleri kursu bir kararın hangi aşamada (ön işlemci, derleyici, bağlayıcı, koşum) verildiğini ölçtü. Bu kurs farklı bir soruyla başlıyor: kaynakta hiçbir aşama bir bellek bloğunu kimin bırakacağını söylemiyor. Derleyici bir bildirimin tipini bilir, bağlayıcı bir adın hangi tanıma bağlandığını çözer, koşum yalnız sonucu görür — ama “bu bloğu kim serbest bırakacak” sorusunun yanıtı hiçbirinde durmuyor. Bu yanıt, kaynakta yazılı bir sözleşmeyle taşınıyor: bir başlık dosyasındaki yorum, bir işlev adı, bir alışkanlık.

Kurs bunu tek bir soruyla ölçüyor: bu belleği kim sahipleniyor? Yanıt dört sınıftan birine düşüyor ve bu dört ad kurs boyunca değişmiyor.

Dört Sınıf: Dil, Çağıran, Çağrılan, Sahipsiz

Sınıf Tanım
dil Ömrü dil belirler (otomatik ya da statik); kimse bırakma çağrısı yapmaz
çağıran Belleği ayıran ya da bir işlevden alan taraf bırakmakla yükümlüdür
çağrılan Belleği bir modül tutar; çağırana yalnız ödünç verir, hata yolunda da kendisi temizler
sahipsiz Hiçbir taraf bırakmakla yükümlü değildir; blok yalnız bir sayaç farkı olarak görünür

Bu dört sınıfı beş dosyalık bir ölçüm çekirdeği kuruyor: sayac.h/sayac.c her ayırma ve bırakma çağrısını sayan ince bir sarmalayıcı, sahip.h/sahip.c opak bir kaydın sahiplik sözleşmesini yorumlarda taşıyan bir birim, sahiplik.c ise on üç gözlemi tek bir koşumda toplayan ölçüm işlevi. Kursun geri kalan on dört dersi bu çekirdeğin kesitlerini alacak; bu ders tek istisna olarak tamamını çalıştırıyor.

// sayac.h — her ayirmayi ve her birakmayi sayan sarmalayici
#ifndef SAYAC_H
#define SAYAC_H

#include <stddef.h>

void *say_ayir(size_t boyut);
void *say_buyut(void *eski, size_t boyut);
void say_birak(void *blok);
int ayrilan_sayisi(void);
int birakilan_sayisi(void);

#endif
// sayac.c — sayaclar yalniz bu birimde tutulur
#include <stdlib.h>
#include "sayac.h"

static int ayrilan = 0;
static int birakilan = 0;

void *say_ayir(size_t boyut) {
    void *blok = malloc(boyut);
    if (blok != NULL) {
        ayrilan++;
    }
    return blok;
}

void *say_buyut(void *eski, size_t boyut) {
    int yeni_blok = (eski == NULL);
    void *yeni = realloc(eski, boyut);
    if (yeni != NULL && yeni_blok) {
        ayrilan++;
    }
    return yeni;
}

void say_birak(void *blok) {
    if (blok != NULL) {
        birakilan++;
    }
    free(blok);
}

int ayrilan_sayisi(void) {
    return ayrilan;
}

int birakilan_sayisi(void) {
    return birakilan;
}
// sahip.h — kaydin ici gizli, sahiplik sozlesmesi yorumlarda yazili
#ifndef SAHIP_H
#define SAHIP_H

#include <stddef.h>

typedef struct Kayit Kayit;

/* Cagiran sahiplenir ve kayit_yok_et ile birakir. Ad sinira sigmazsa NULL
   doner; o ana kadar ayrilan bellegi islev kendisi birakir. */
Kayit *kayit_olustur(const char *ad, size_t sinir);

/* Kaydi ve kaydin icindeki ad tamponunu birlikte birakir. */
void kayit_yok_et(Kayit *k);

/* Kayit sahiplenir: donen isaretci kayit yok edilene dek gecerlidir,
   cagiran onu birakmaz. */
const char *kayit_ad(const Kayit *k);

/* Cagiran sahiplenir: donen kopya say_birak ile birakilir. */
char *kayit_ad_kopyasi(const Kayit *k);

#endif
// sahip.c — sozlesmenin uygulandigi birim
#include <string.h>
#include "sahip.h"
#include "sayac.h"

struct Kayit {
    char *ad;
    size_t uzunluk;
};

Kayit *kayit_olustur(const char *ad, size_t sinir) {
    Kayit *k = say_ayir(sizeof *k);
    if (k == NULL) {
        return NULL;
    }
    k->uzunluk = strlen(ad);
    if (k->uzunluk >= sinir) {
        say_birak(k);
        return NULL;
    }
    k->ad = say_ayir(k->uzunluk + 1);
    if (k->ad == NULL) {
        say_birak(k);
        return NULL;
    }
    memcpy(k->ad, ad, k->uzunluk + 1);
    return k;
}

void kayit_yok_et(Kayit *k) {
    if (k == NULL) {
        return;
    }
    say_birak(k->ad);
    say_birak(k);
}

const char *kayit_ad(const Kayit *k) {
    return k->ad;
}

char *kayit_ad_kopyasi(const Kayit *k) {
    char *kopya = say_ayir(k->uzunluk + 1);
    if (kopya != NULL) {
        memcpy(kopya, k->ad, k->uzunluk + 1);
    }
    return kopya;
}
// sahiplik.c — bir bellegi kimin sahiplendigini olcen cekirdek
#include <stdio.h>
#include <string.h>
#include "sahip.h"
#include "sayac.h"

static int statik_sayac = 3;

static int fark(void) {
    return ayrilan_sayisi() - birakilan_sayisi();
}

static void gozle(const char *ad, const char *sinif, const char *kanit) {
    printf("%-30s%-10s%s\n", ad, sinif, kanit);
}

int main(void) {
    char tampon[64];
    int dil = 0, cagiran = 0, cagrilan = 0, sahipsiz = 0;
    int once, ayrilan_once, birakilan_once;

    printf("%-30s%-10s%s\n", "gozlem", "sahip", "kanit");

    once = fark();
    int yerel = 5;
    int *p = &yerel;
    snprintf(tampon, sizeof tampon, "*p %d, sayac farki %d", *p, fark() - once);
    gozle("yerel degisken", "dil", tampon);
    dil++;

    int *s = &statik_sayac;
    snprintf(tampon, sizeof tampon, "*s %d, birakma cagrisi yok", *s);
    gozle("statik alan", "dil", tampon);
    dil++;

    const char *degismez = "sabit";
    snprintf(tampon, sizeof tampon, "uzunluk %d, birakma cagrisi yok", (int)strlen(degismez));
    gozle("dizgi degismezi", "dil", tampon);
    dil++;

    int dizi[4] = {1, 2, 3, 4};
    int *ilk = dizi;
    int *son = &dizi[3];
    snprintf(tampon, sizeof tampon, "son - ilk %d, *son %d", (int)(son - ilk), *son);
    gozle("yerel dizi", "dil", tampon);
    dil++;

    once = fark();
    int *blok = say_ayir(4 * sizeof *blok);
    int toplam = 0;
    if (blok != NULL) {
        for (int i = 0; i < 4; i++) {
            blok[i] = i + 1;
            toplam += blok[i];
        }
    }
    say_birak(blok);
    snprintf(tampon, sizeof tampon, "toplam %d, sayac farki %d", toplam, fark() - once);
    gozle("ayir ve birak", "cagiran", tampon);
    cagiran++;

    once = fark();
    int *buyuyen = say_buyut(NULL, 2 * sizeof *buyuyen);
    int korunan = 0;
    if (buyuyen != NULL) {
        buyuyen[0] = 7;
        int *yeni = say_buyut(buyuyen, 8 * sizeof *yeni);
        if (yeni != NULL) {
            buyuyen = yeni;
        }
        korunan = buyuyen[0];
    }
    say_birak(buyuyen);
    snprintf(tampon, sizeof tampon, "ilk eleman %d, sayac farki %d", korunan, fark() - once);
    gozle("yeniden boyutlandirma", "cagiran", tampon);
    cagiran++;

    ayrilan_once = ayrilan_sayisi();
    Kayit *k = kayit_olustur("defter", 16);
    snprintf(tampon, sizeof tampon, "bos degil %d, ayrilan blok %d", k != NULL, ayrilan_sayisi() - ayrilan_once);
    gozle("opak kayit", "cagiran", tampon);
    cagiran++;

    once = fark();
    char *kopya = kayit_ad_kopyasi(k);
    snprintf(tampon, sizeof tampon, "kopya %s", kopya != NULL ? kopya : "yok");
    say_birak(kopya);
    snprintf(tampon + strlen(tampon), sizeof tampon - strlen(tampon), ", sayac farki %d", fark() - once);
    gozle("donen kopya", "cagiran", tampon);
    cagiran++;

    once = fark();
    const char *odunc = kayit_ad(k);
    snprintf(tampon, sizeof tampon, "ad %s, sayac farki %d", odunc, fark() - once);
    gozle("odunc verilen ad", "cagrilan", tampon);
    cagrilan++;

    once = fark();
    Kayit *uzun = kayit_olustur("cok uzun bir kayit adi", 8);
    snprintf(tampon, sizeof tampon, "bos %d, sayac farki %d", uzun == NULL, fark() - once);
    gozle("hata yolunda temizlik", "cagrilan", tampon);
    cagrilan++;

    birakilan_once = birakilan_sayisi();
    kayit_yok_et(k);
    snprintf(tampon, sizeof tampon, "birakilan blok %d", birakilan_sayisi() - birakilan_once);
    gozle("kaydin ic tamponu", "cagrilan", tampon);
    cagrilan++;

    once = fark();
    int *kayip = say_ayir(sizeof *kayip);
    kayip = say_ayir(sizeof *kayip);
    say_birak(kayip);
    snprintf(tampon, sizeof tampon, "sayac farki %d", fark() - once);
    gozle("uzerine yazilan isaretci", "sahipsiz", tampon);
    sahipsiz++;

    Kayit *k2 = kayit_olustur("taslak", 16);
    once = fark();
    if (k2 != NULL) {
        kayit_ad_kopyasi(k2);
    }
    snprintf(tampon, sizeof tampon, "sayac farki %d", fark() - once);
    kayit_yok_et(k2);
    gozle("yok sayilan donus", "sahipsiz", tampon);
    sahipsiz++;

    printf("\n%-10s%d\n", "dil", dil);
    printf("%-10s%d\n", "cagiran", cagiran);
    printf("%-10s%d\n", "cagrilan", cagrilan);
    printf("%-10s%d\n", "sahipsiz", sahipsiz);
    printf("toplam gozlem: %d\n", dil + cagiran + cagrilan + sahipsiz);
    printf("ayrilan %d birakilan %d fark %d\n", ayrilan_sayisi(), birakilan_sayisi(), fark());
    return 0;
}
gozlem                        sahip     kanit
yerel degisken                dil       *p 5, sayac farki 0
statik alan                   dil       *s 3, birakma cagrisi yok
dizgi degismezi               dil       uzunluk 5, birakma cagrisi yok
yerel dizi                    dil       son - ilk 3, *son 4
ayir ve birak                 cagiran   toplam 10, sayac farki 0
yeniden boyutlandirma         cagiran   ilk eleman 7, sayac farki 0
opak kayit                    cagiran   bos degil 1, ayrilan blok 2
donen kopya                   cagiran   kopya defter, sayac farki 0
odunc verilen ad              cagrilan  ad defter, sayac farki 0
hata yolunda temizlik         cagrilan  bos 1, sayac farki 0
kaydin ic tamponu             cagrilan  birakilan blok 2
uzerine yazilan isaretci      sahipsiz  sayac farki 1
yok sayilan donus             sahipsiz  sayac farki 1

dil       4
cagiran   4
cagrilan  3
sahipsiz  2
toplam gozlem: 13
ayrilan 11 birakilan 9 fark 2

IS1. On üç gözlemin 4’ü dil sınıfında, 4’ü çağıran’da, 3’ü çağrılan’da ve 2’si sahipsiz’de topladı. dil sınıfının dördünde de bir bırakma çağrısı yok — yerel değişken, statik alan, dizgi değişmezi ve yerel dizi kendi ömrünü kendisi bitiriyor. ayrilan 11 birakilan 9 fark 2 satırı, koşumun sonunda hâlâ iki bloğun bırakılmadan kaldığını söylüyor; bu iki blok, sahipsiz satırlarının bıraktığı fark ile birebir eşleşiyor — üzerine yazılan işaretçi ve yok sayılan dönüş, ikisi de tam bir blok kaybetti. Bu koşum uyarısız derlendi ve üç ayrı koşumda birebir aynı çıktıyı verdi; kursun geri kalan her dersi bu dört sınıfı ve bu tabloyu kaynak alacak.

Adres Alma ve Dolaylama: yerel degisken Satırının Derinleştirilmesi

Çekirdeğin ilk satırı — yerel degisken — iki işlecin en sık kullanılan çiftini içeriyor: adres alma (&) ve dolaylama (*). Bu ders onları çekirdeğin dışında, yalnız bu ikisine odaklanan küçük bir koşumla ayrıca ölçüyor.

// adres_alma.c — adres alma ve dolaylama, adres basmadan esitlik ve deger ile olculur
#include <stdio.h>

int main(void) {
    int sayi = 42;
    int *p = &sayi;

    printf("%s %d\n", "isaret edilen deger", *p);
    printf("%s %d\n", "p sayinin adresini mi gosteriyor", (int)(p == &sayi));

    *p = 100;
    printf("%s %d\n", "dolaylamayla yazilan yeni deger", sayi);

    int baska = 7;
    p = &baska;
    printf("%s %d\n", "yeniden atamadan sonra isaret edilen deger", *p);
    printf("%s %d\n", "p artik sayiyi mi gosteriyor", (int)(p == &sayi));
    return 0;
}
isaret edilen deger 42
p sayinin adresini mi gosteriyor 1
dolaylamayla yazilan yeni deger 100
yeniden atamadan sonra isaret edilen deger 7
p artik sayiyi mi gosteriyor 0

IS2. &sayi ifadesi sayi değişkeninin adresini üretiyor ve bu adres p’ye atanıyor; p == &sayi karşılaştırması 1 veriyor, yani p gerçekten sayi’yi gösteriyor — ama bu karşılaştırma adresin kendisini hiç yazdırmadan, yalnız iki adresin eşit olup olmadığını soruyor. *p = 100 satırı dolaylama işlecini bir atamanın sol tarafında kullanıyor: p’nin gösterdiği yere yazıyor, p’nin kendisine değil. Bunun kanıtı bir sonraki satır — sayi artık 100; p üzerinden yapılan yazma, gösterdiği değişkeni gerçekten değiştirdi. p = &baska ile p başka bir değişkeni göstermeye yeniden atanabiliyor; bu atamadan sonra *p 7’yi veriyor ve p == &sayi artık 0 — p’nin kendisi bir değişken, istenildiğinde başka bir adresi tutmak üzere değiştirilebilir.

IS3. int *p bildirimi, p’nin bir int’in adresini tutacağını söylüyor — ama bu tip, p’nin gösterdiği belleğin nasıl elde edildiğini hiç söylemiyor. Yukarıdaki koşumda p önce bir yerel değişkeni (sayi), sonra bir başkasını (baska) gösterdi; ikisi de dil sınıfındaydı ve kimse onları bırakmadı. Ama int * tipi, dinamik olarak ayrılmış bir bloğu göstermek için de kullanılabilir — çekirdekteki ayir ve birak satırı tam olarak bunu yaptı: int *blok = say_ayir(...). Aynı bildirim, aynı işleçler (*, &), iki farklı sahiplik sınıfı. Bu, dersin sınırlayıcı ölçümüdür.

İşaretçiye İşaretçi: Dolaylamanın İkinci Katmanı

Yukarıdaki p değişkeni bir int’i gösteriyordu; ama bir işaretçinin kendisi de bir değişken olduğuna göre, onun adresini tutan başka bir işaretçi de yazılabilir. Bu, tek bir * yerine iki tane kullanmayı gerektiriyor ve dolaylamayı bir kat daha derinleştiriyor.

// cift_isaretci.c — bir isaretciye isaretci, dolaylamayi bir kat daha uygular
#include <stdio.h>

int main(void) {
    int sayi = 9;
    int *p = &sayi;
    int **pp = &p;

    printf("%s %d\n", "iki kat dolaylamayla okunan deger", **pp);
    printf("%s %d\n", "pp p'nin adresini mi gosteriyor", (int)(*pp == p));

    **pp = 30;
    printf("%s %d\n", "iki kat dolaylamayla yazilan yeni deger", sayi);

    int baska = 4;
    *pp = &baska;
    printf("%s %d\n", "pp uzerinden p'yi degistirince p'nin gosterdigi deger", *p);
    printf("%s %d\n", "p artik sayiyi mi gosteriyor", (int)(p == &sayi));
    return 0;
}
iki kat dolaylamayla okunan deger 9
pp p'nin adresini mi gosteriyor 1
iki kat dolaylamayla yazilan yeni deger 30
pp uzerinden p'yi degistirince p'nin gosterdigi deger 4
p artik sayiyi mi gosteriyor 0

IS4. int **pp bildirimi, pp’nin bir int *’in adresini tuttuğunu söylüyor — pp, p’yi gösteriyor, p de sayi’yi gösteriyor. **pp iki dolaylama işlecini art arda uyguluyor: önce *pp ile p’nin taşıdığı adrese ulaşılıyor, sonra ikinci * ile o adresteki değere. **pp = 30 satırı bu iki katmanlı yolu bir atamanın sol tarafında kullanıyor ve sayi gerçekten 30 oluyor — tıpkı *p = 100’ün sayi’yi değiştirdiği gibi, ama araya p giriyor. Son iki satır *pp’nin, yani p’nin kendisinin, yeniden atanabildiğini gösteriyor: *pp = &baska ataması p’yi baska’yı göstermeye çeviriyor — pp üzerinden, p’ye doğrudan erişmeden. Bu ayrım, bir işlevin çağıranın işaretçisini kendisi değiştirmek istediği durumlarda önem kazanıyor: int * alan bir işlev yalnız gösterilen değeri değiştirebilir, int ** alan bir işlev çağıranın işaretçisini başka bir adrese çevirebilir. Sınıf yine dil: üçü de yerel değişken, hiçbiri ayrılmadı.

Sınırlayıcı Ölçüm

Bir işaretçinin tipi, gösterdiği bloğun kim tarafından bırakılacağını söylemez. int * bir yerel değişkeni de gösterebilir, say_ayir ile ayrılmış bir bloğu da — kaynakta ikisi de aynı görünür ve derleyici aralarında hiçbir ayrım yapmaz. Yukarıdaki iki koşumda p değişkeni üç farklı anda üç farklı şeyi gösterdi: bir yerel değişken, başka bir yerel değişken ve (çekirdekte) ayrılmış bir blok. Üçünde de bildirim aynıydı (int *). Bir işaretçiyi okuyan biri, tipine bakarak sahiplik sınıfını çıkaramaz — bu bilgi ancak değişkenin nereden geldiğine (bir & ifadesinden mi, bir ayırma çağrısından mı) bakılarak ya da onu belgeleyen sözleşme okunarak öğrenilebilir. Kursun geri kalanı, bu sözleşmenin nasıl yazıldığını ve nasıl kırıldığını ölçüyor.

Özet

  • Bir bellek bloğunun sahibi dört sınıftan biridir: dil, çağıran, çağrılan, sahipsiz; bu ders dil sınıfını çekirdeğin tam koşumuyla ölçüyor.
  • Beş dosyalık ölçüm çekirdeği (sayac.h, sayac.c, sahip.h, sahip.c, sahiplik.c) on üç gözlemi 4 dil · 4 çağıran · 3 çağrılan · 2 sahipsiz olarak dağıtıyor; toplam fark 2.
  • Adres alma (&) bir değişkenin adresini üretir; dolaylama (*) o adresteki değeri okur ya da ona yazar — ikisi de adres basılmadan, eşitlik ve değerle ölçülebilir.
  • Bir işaretçi yeniden atanabilir ve farklı zamanlarda farklı değişkenleri gösterebilir; bu, işaretçinin kendisiyle gösterdiği değer arasındaki farktır.
  • Bir işaretçinin tipi, gösterdiği bloğun kim tarafından bırakılacağını söylemez — aynı int * bildirimi hem bir yerel değişkeni hem ayrılmış bir bloğu gösterebilir.

Sonraki Adım

Bu derste ölçülen yerel dizi satırı, son - ilk farkını 3 olarak verdi ama bu farkın nasıl hesaplandığı üzerinde durulmadı. Bir sonraki ders, İşaretçi Aritmetiği, bu farkın neden bayt cinsinden değil eleman cinsinden olduğunu ve bir işaretçiyi artırmanın tip boyutuna göre nasıl ilerlediğini ölçüyor — hâlâ dil sınıfında kalarak, ama artık tek bir değere değil bir dizinin elemanları arasındaki ilişkiye bakarak.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat