Forum: Ders Arası RSS
Bellek Yönetimi dersinden çıkarttıklarım
acehreli (Moderatör) #1
Kullanıcı başlığı: Ali Çehreli
Üye Haz 2009 tarihinden beri · 4527 mesaj
Grup üyelikleri: Genel Moderatörler, Üyeler
Profili göster · Bu konuya bağlantı
Konu adı: Bellek Yönetimi dersinden çıkarttıklarım
Bellek Yönetimi dersi beni çok uğraştırdı. Aşağıdaki metinler de bir noktada o derse dahildiler. Çok uzadığı ve konudan ayrıldığı için bunları çıkartmaya karar verdim.

Metnin düzeni buraya tam uygun değil ama okunması zor da değil: $(C ) gördüğünüz zaman metin içinde geçen kod anlamına geliyor. $(I ) italic, $(ASIL ) İngilizce aslı, vs.

Bellek

Önceki bölümlerde ve özellikle $(LINK2 /ders/d/bit_islemleri.html, Bit İşlemleri dersinde) en küçük bilgi biriminin bit olduğunu ve sekizer bitin bir araya gelerek baytları oluşturduklarını görmüştük. Adreslenebilen, yani bellekte ayrı ayrı erişilebilen en küçük veri birimi bayttır.

Belleğin büyüklüğü

Bir çok derste belleği soldan sağa doğru yan yana baytlar olarak hayal ettik:




  adresler →   0x00000000 0x00000001 0x00000002         0xffffffff
              +----------+----------+----------+- ... -+----------+
   baytlar →  |          |          |          |       |          |
              +----------+----------+----------+- ... -+----------+


 Adres değerleri geleneksel olarak on altılı sistemde yazıldığından yukarıdaki şekildeki adresleri de on altılı sistemde yazdım.

32 bitlik bir mikro işlemci ancak 32 bitlik adresler kullanabildiği için böyle bir sistemdeki en yüksek adres değeri $(C uint.max)'ın da değeri olan 4,294,967,295'tir (4 giga bayt). 64 bitlik sistemlerde ise kabaca $(I 4 milyar çarpı 4 milyar) kadar bellek adreslenebilir. O değer de tam olarak $(C ulong.max)'ın değeridir.

Doğal olarak, belirli bir sistemde ne kadar bellek adreslenebildiğinden bağımsız olarak ancak gerçekten var olan miktarda bellek kullanılabilir. Ancak, $(I gerçekten var olan bellek) kavramı çoğu ortamda bilgisayarın fiziksel olarak sahip olduğundan daha fazladır. Örneğin fiziksel olarak 1 giga bayt belleği bulunan bir bilgisayar toplam 2 giga bayt varmış gibi kullanılabilir. Bu, işletim sistemlerinin sunduğu $(I sanal bellek) $(ASIL virtual memory) düzeneği ile sağlanır. Sanal bellek, belirli bir anda kullanılmayan bellek bölümlerini sabit diske kaydeder.

Bellek, programlardan bütünüyle soyutlanmıştır. Örneğin programa görünen bir adres değerinin fiziksel bellekteki gerçek adres değeri ile hiçbir ilgisi olmayabilir. Programlar gerektikçe yer edinirler ve kullanırlar. O yeri sağlayan düzeneğin programların işleyişleri açısından önemi yoktur.

Bellek hataları

Genel olarak programlarda karşılaşılan hataların büyük bir bölümü bellekle ilgilidir. D'nin kod güvenliğini ön planda tutan bir dil olması nedeniyle D programlarında bellek hataları az görülür.

İlklenmemiş değerler

Değişkenler, yaşamları sona ermiş olan eski değişkenlerin eski yerlerinde kurulurlar. Bu yerlerdeki baytların önceki kullanımlarından kalmış olan değerleri çoğu başka programlama dilinde sorun oluşturur: O baytların eski değerleri yeni değişkenin anlamsız veya hatalı değer taşımasına neden olur. Başka dillerde bu tür hataları önlemek için değişkenlerin açıkça programcı tarafından ilklenmeleri gerekir. D'de ise değişkenler otomatik olarak ilklendiklerinden bu tür hatalarla karşılaşılmaz.

Buna rağmen, D'de de ilklenmemiş bellek bölgeleri edinilebilir. Sabit uzunluklu bir diziyi $(C void)'e eşitleyerek kurmak, o dizinin elemanlarının ilklenmelerini önler:

    ubyte[100] bellek = void;

İlklenmemiş bellek bölgelerinin bir sorunu, yaşamları aslında sona ermiş olan başka nesnelerin referanslarını barındırıyor olabilecekleridir. Çöp toplayıcı, hâlâ kullanımda olduklarını düşüneceği için o başka nesneleri geç sonlandırabilir veya hiç sonlandırmayabilir.

Çöp toplayıcıya ait olan bir bellek bölgesi ayırmaya yarayan $(C GC.malloc()) işlevinin döndürdüğü bölge de ilklenmiş değildir:

import core.memory;
// ...
    void * bellek = GC.malloc(100);

Baytların başka nesnelere referans olarak algılanma sorunu bu kullanım için de geçerlidir. ($(I Not: Aşağıdaki örneklerde belleği sıfırladığı için daha güvenli olan $(C GC.calloc())'u kullanacağım.))

D programları C'nin bellek ayırmaya yarayan standart $(C std.c.stdlib.malloc) işlevini de çağırabilirler. Onun döndürdüğü bellek de ilklenmiş değildir:

import std.c.stdlib;
// ...
    void * p = malloc(100);

$(C malloc())'un ek bir sorunu, çöp toplayıcının onun ayırdığı bellekten haberinin olmamasıdır. Çöp toplayıcı $(C std.c.stdlib.malloc) ile ayrılmış olan belleğin içindeki referanslara normalde bakmaz ve o referansların eriştirdikleri nesneleri istediğimizden daha erken sonlandırabilir. Çöp toplayıcının C'nin $(C malloc())'u ile ayrılmış olan bellek bölgesinden haberinin olması için $(C GC.addRange()) çağrılır. Bellek $(C std.c.stdlib.free) ile geri verilirken de $(C GC.removeRange())'in çağrılması unutulmamalıdır.

Hatalara açık olduğundan ilklenmemiş bellek bölgeleri kullanmamanızı öneririm.

Bellek sızıntısı

Bir programın işi biten bir nesneye ait belleği geri vermeyi unutması, $(I bellek sızıntısı) $(ASIL memory leak) denen sorunu oluşturur. Böyle bir hatası bulunan bir program, çalışma süresine ve sızan belleğin büyüklüğüne bağlı olarak bir süre sonra bilgisayarın bütün belleğini elinde tutuyor olabilir. Sonunda ne kendisi için ne de sistemdeki diğer programlar için bellek kalır.

Geri verilmesi unutulan bellek aslında program sonlandığında tekrar işletim sistemine geçer. Burada sorun, programın çalışması sırasında başka işler için bellek kalmamasıdır.

D gibi çöp toplayıcılı dillerde bellek sızıntısı sorunları oldukça az yaşanır. Buna rağmen karşılıklı referans $(ASIL cyclic reference) sorunu D'de görülebilir: Artık kullanılmıyor bile olsalar, birbirlerine erişim sağlayan nesneler çöp toplayıcıya hâlâ kullanımdaymışlar gibi görüneceklerinden hiç sonlandırılmazlar.

Bellek bozulması

Yanlış adresteki bir değerin değiştirilmesine $(I bellek bozulması) $(ASIL memory corruption) denir. Programın kendisi de bellekte olduğu için bu yanlışlık programın da bozulmasına neden olabilir. $(LINK2 /ders/d/islevler_diger.html, Diğer İşlev Olanakları dersinde) gördüğümüz $(C @safe) bu tür hataları gidermede çok etkilidir. Ayrıca göstergeler D'de yaygın olmadıklarından D'de bellek bozulması hataları da çok azdır.

Bazı durumlarda D'de de belleğin yanlış yerlerine yazılabilir. Örneğin, aşağıdaki program dizinin dışındaki bir elemana yazmaya çalıştığı için hatalıdır:

import std.stdio;
 
void main()
{
    int i;
    int[1] dizi;
    int j;
 
    *(dizi.ptr + 1) = 42;      // ← HATA
 
    writefln("i: %s, j: %s", i, j);
}

Dizide tek eleman bulunduğu için dizinin yalnızca $(C dizi.ptr) adresindeki tek elemanına yazılabilir. Bir sonraki var olmayan elemana yazmaya çalışan yukarıdaki kod bu yüzden hatalıdır. Bu örnekteki hatanın, dizi ile hiçbir ilgisi bulunmayan ama tesadüfen bellekte diziden hemen sonra bulunan $(C j)'nin değerinin bozulmasına neden olduğunu görüyoruz:


i: 0, j: 42


D'de normalde kullanılan $(C []) işleci bu tür hataları bazen derleme zamanında yakalar:

    dizi[1] = 42;              // ← HATA 

Derleme hatası, dizi indeksinin sınır dışında olduğunu bildirir:


Error: array index 1 is out of bounds dizi[0 .. 1]


Derleme zamanında algılanamadığı zamanlarda ise hata $(C []) işleci tarafından çalışma zamanında yakalanır:

core.exception.RangeError@deneme: Range violation

Yaşam süreçleri

Daha önceki derslerde değişkenlerin yaşam süreçleri, kurulmaları ve sonlandırılmaları ile ilgili işlemlere değinmiştik. Bellek işlemlerini de düşününce, bir değişkenin yaşamı ile ilgili yedi genel işlem olduğunu düşünebiliriz:


 * Yerinin ayrılması
 * İlklenmesi
 * Kurulması
 * Kullanılması
 * Yaşamının sona ermesi
 * Sonlandırılması
 * Yerinin geri verilmesi

Yaşam süreci üç işlemle başlar:

 * Yerinin ayrılması: Bu, değişkenin yerleştirileceği bellek bölgesinin ayrılmasıdır. Değişkenler çeşitlerine göre farklı bellek bölgelerinde oluşturulurlar.

Çoğu durumda gerçekten bellekten yer ayrılıyor olsa da, derleyici programın daha hızlı çalışmasını sağlamak için bazı değişkenleri bütünüyle mikro işlemci yazmaçlarında da yaşatabilir.

int hesap(int sayı)
{
    int sonuç = sayı + 42;
    return sonuç;
}

Örneğin yukarıdaki gibi kısa ve basit işlevlerde bazı değişkenler için hiç bellek kullanılmaz. Özellikle $(C sayı) ve $(C sonuç) gibi temel türlerden olan değişkenler bütünüyle mikro işlemci yazmaçları üzerinde gerçekleştirilebilirler.

Öte yandan, yukarıda da değindiğim gibi, adres değerinin alınmış olması bile bir değişkenin gerçekten bellekte yaşamasına neden olur.

 * İlklenmesi: Değişkenin ve varsa üyelerinin ilk değerlerini almalarıdır. Değişken kurulurken bir değer belirtilmişse bu adım atlanabilir.

 * Kurulması: Eğer varsa, uygun kurucusu işletilerek değişkenin kullanıma hazırlanmasıdır.

Bir değişkenin programda kullanılabilmesi için o üç işlemin yukarıda yazılan sırada ve değişkenin herhangi bir şekilde kullanılmasından önce işletilmeleri şarttır. Değişkenin yaşamı bu üç adımla başlar. Her üç adım da derleyicinin oluşturduğu kodlar sayesinde perde arkasında otomatik olarak işletilirler:

int sayı;                       // 0 ile ilklenir
int başkaSayı = 42;             // belirtilen değerle ilklenir
double kesirli;                 // double.nan ile ilklenir
auto nesne = new Sınıf(renk);   // kurucu işlevi ile kurulur 

O değişkenlerin her birisi için perde arkasında yer ayrılmış ve her birisi kullanıma hazırlanmıştır.

Kurulmuş olan bir değişken için bu noktadan sonra genel olarak iki işlem vardır:

 * Kullanılması: Değişkenin programdaki ifadelerde ve deyimlerde yer almasıdır.

 * Yaşamının sona ermesi: Değişkenin geçersiz hale gelmesidir.

Yaşam süreci, değişkenin programda yasal olarak kullanılabildiği süredir. Örneğin değişkenin tanımlanmış olduğu kapsamdan çıkılması o değişkenin yaşamının sona ermesine neden olur:

double kareleriniTopla(double[] sayılar)
{
    double toplam;
 
    foreach (sayı; sayılar) {
        double karesi = sayı * sayı;
        toplam += karesi;
    }                      // ← karesi'nin yaşamı sona erer
 
    return toplam;
}                          // ← toplam'ın yaşamı sona erer 

Yaşamı sona eren değişkenler için yine perde arkasında şu temizlik işlemleri işletilir:

 * Sonlandırılması: Temel türlerde hiçbir özel işlem gerekmez. Yapı veya sınıf nesnelerinde ise eğer tanımlanmışsa o türün sonlandırıcı işlevi işletilir.

Dinamik nesnelerin sonlandırıcıları ilerideki belirsiz bir zamanda işletilir.

 * Yerinin geri verilmesi: Değişkeni barındıran bellek daha sonradan başka değişkenler için kullanılmak üzere geri verilir.

Dinamik değişkenler için bu işlem de ilerideki belirsiz bir zamanda işletilir.

Yaşamın uzatılması

Bazı değişkenlerin yaşam süreçleri onlara erişim sağlamakta olan başka değişkenler tarafından otomatik olarak uzatılır.

Bunun bir örneğini $(LINK2 /ders/d/kapamalar.html, İşlev Göstergeleri ve Kapamalar dersinde) $(C delegate) anahtar sözcüğü ile görmüştük. Otomatik bir değişkenin yaşamı normalde onu tanımlayan kapsamdan çıkıldığında sona erer:

import std.stdio;
 
void birİşlev()
{
    int i;
} // ← i'nin yaşamı sona erer
 
void main()
{
    birİşlev();
}

Bir kapamanın kullanmakta olduğu yerel bir değişken ise o kapama geçerli olduğu sürece yaşamaya devam eder. O yüzden aşağıdaki işlevin içinde tanımlanmış olan $(C i)'nin yaşamı uzar:

import std.stdio;
 
alias int delegate(int) Hesapçı;
 
Hesapçı birİşlev()
{
    int i;
 
    return (int parametre) {
        return parametre + i; // ← i'yi kullanan bir kapama
    };
} // ← i'nin yaşamı artık bu noktada sonlanmaz
 
void main()
{
    Hesapçı hesapçı = birİşlev();
    hesapçı(42);
}

Normalde hemen sonlandırılacak olan değişkenlerin yaşamları $(C new) ifadesi ile de uzatılabilir. Örneğin, kaç kere kullanıldığını kendisine verilen bir sayacı arttırarak belirleyen bir üye işleve bakalım:

struct SayanYapı
{
    int * sayaç;
 
    this(int * sayaç)
    {
        this.sayaç = sayaç;
    }
 
    void kullan()
    {
        // Gösterilen sayacı arttırıyoruz
        ++(*sayaç);
    }
}

Nesnenin hangi sayacı kullanacağı nesne kurulurken parametre olarak veriliyor ve o sayaç $(C kullan()) işlevinde arttırılıyor.

Şimdi de o türün birden fazla nesnesini oluşturan bir işlev düşünelim. Bu işlevin oluşturduğu bütün nesneler aynı sayacı arttıracaklar:

SayanYapı[] sayaçPaylaşanNesnelerOluştur(int adet)
{
    SayanYapı[] sonuç;
 
    // Bu sayaç bütün nesneler tarafından paylaşılacak
    int * paylaşılanSayaç = new int;
 
    // Hepsi aynı sayacı paylaşıyorlar
    foreach (i; 0 .. adet) {
        sonuç ~= SayanYapı(paylaşılanSayaç);
    }
 
    return sonuç;
}

O programdaki sayaç tek dinamik değişken olarak oluşturuluyor ve kurulan bütün nesnelere veriliyor. Bütün nesneler de aynı sayaç göstergesi ile kuruldukları için onu ortaklaşa kullanıyorlar.

$(C paylaşılanSayaç) $(C new) ile dinamik olarak oluşturulmasaydı yaşamı işlevden çıkıldığında son bulurdu ve nesneler o durumda geçersiz bir sayacı kullanmış olacaklarından program hatalı olurdu.

Ali
Ali #2
Kullanıcı başlığı: Python / Java / C,C++  / D
Üye Ock 2011 tarihinden beri · 84 mesaj · Konum: Orjin
Grup üyelikleri: Üyeler
Profili göster · Bu konuya bağlantı
Abi ellerine saglik (: gercekten guzel olmus bu dershanedekini okudum.

new ve delete operatörleri ile dinamik hafiza işlemlerini görmüştüm C++'ta D icin bellek yonetimini gormemistim. Bunu iyice bir tekrar edeyim cok tesekkur ederim.
Ali
acehreli (Moderatör) #3
Kullanıcı başlığı: Ali Çehreli
Üye Haz 2009 tarihinden beri · 4527 mesaj
Grup üyelikleri: Genel Moderatörler, Üyeler
Profili göster · Bu konuya bağlantı
CWSuskun:
guzel olmus bu

Hem çok zorlandım hem de deneyimim olmadığı için tam doğru olarak anlattığımdan da emin değilim aslında. :-/

new ve delete operatörleri ile dinamik hafiza işlemlerini görmüştüm C++'ta

Ben de görmüştüm ama ne kendim kullandım ne de kullananı gördüm. :)

D icin bellek yonetimini gormemistim.

Bilinmesi gerekiyor ama günlük programlamada ihtiyaç olacağını hiç sanmıyorum. Phobos modüllerine baktığınızda emplace(), malloc(), GC.malloc(), vs. çağrılar göreceksiniz. Hiç olmazsa ne olup bittiğini anlayabiliyoruz. (Doğrusu ben de bunu yazarken pekiştirdim. :))

Ali
Ali #4
Kullanıcı başlığı: Python / Java / C,C++  / D
Üye Ock 2011 tarihinden beri · 84 mesaj · Konum: Orjin
Grup üyelikleri: Üyeler
Profili göster · Bu konuya bağlantı
Evet abi modullere bakinca gorebiliyoruz. Aslinda tum konularin ardindan moduller adinda bir bolumde acilip yayinlanabilir. Hatta soyle aciklayayim

std.stdio; => bunlari bulundurur gibi.

Dedigin dinamik hafiza tahsisi konusundan: malloc'a baktim. yani D'ye alismak kolay aslinda C,C++ biliyorsn (: realloc goremedim. Tabii bunlar gunluk ihtiyaca tabii degilmis. Hangi ortamlarda mutlak olmali?
Ali
acehreli (Moderatör) #5
Kullanıcı başlığı: Ali Çehreli
Üye Haz 2009 tarihinden beri · 4527 mesaj
Grup üyelikleri: Genel Moderatörler, Üyeler
Profili göster · Bu konuya bağlantı
CWSuskun:
std.stdio; => bunlari bulundurur gibi.

Evet, çok önemli. Zamanında wiki olarak başlatmıştık ama Phobos o kadar hızlı değişti ki şimdilik durulana kadar ara verdik diyelim. :)

  http://ddili.org/wiki/index.php?title=RehberPhobos

realloc goremedim

Aslında ikisinde de var:

import std.c.stdlib;
import core.memory;
 
void main()
{
    void * c = std.c.stdlib.malloc(100);
    void * d = GC.malloc(100);
 
    c = std.c.stdlib.realloc(c, 200);
    d = GC.realloc(d, 200);
 
    std.c.stdlib.free(c);
    GC.free(d);
}

Onu da eklemeliyim, değil mi.

Tabii bunlar gunluk ihtiyaca tabii degilmis. Hangi ortamlarda mutlak olmali?

Çöp toplayıcının belirsizliğinin sorun oluşturduğu bilindiğinde kullanılabilir. new ile yer ayırmak kendi yapacağımızdan daha yavaş olacaktır. Kütüphane yazanlar için önemli olabilir. O yüzden hız açısından kullanılabilir.

Ali
Ali #6
Kullanıcı başlığı: Python / Java / C,C++  / D
Üye Ock 2011 tarihinden beri · 84 mesaj · Konum: Orjin
Grup üyelikleri: Üyeler
Profili göster · Bu konuya bağlantı
Anladim abi, wiki o manade gelisirse guzel olur (:. Aslinda arastirmam lazim benim de... Bildiklerimi oraya eklemem gerekli. Bir bakayim ama dedigin gibi Phobos hizli degisiyorsa (:
Ali
Doğrulama Kodu: VeriCode Lütfen resimde gördüğünüz doğrulama kodunu girin:
İfadeler: :-) ;-) :-D :-p :blush: :cool: :rolleyes: :huh: :-/ <_< :-( :'( :#: :scared: 8-( :nuts: :-O
Özel Karakterler:
Forum: Ders Arası RSS
Bağlı değilsiniz. · Şifremi unuttum · ÜYELİK
This board is powered by the Unclassified NewsBoard software, 20100516-dev, © 2003-10 by Yves Goergen
Şu an: 2017-11-22, 05:03:48 (UTC -08:00)