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.