Giriş: İşaretçiler ve Bellek Yönetiminin Temelleri
Programlamada, özellikle C ve C++ gibi düşük seviyeli dillerde, bellek yönetimi hayati bir konudur. Bu noktada işaretçiler ve dinamik bellek tahsisi kavramları öne çıkar. İşaretçiler, değişkenlerin bellek adreslerini tutan özel değişkenlerdir. Bu sayede programcılar, belleğe doğrudan erişebilir, veri yapılarını daha esnek bir şekilde yönetebilir ve bazı durumlarda program performansını optimize edebilirler. Dinamik bellek yönetimi ise, programın çalışma zamanında ihtiyaca göre bellek ayırma ve serbest bırakma yeteneğidir. Statik veya otomatik olarak tahsis edilen belleğin aksine, dinamik bellek (heap) programcı tarafından açıkça yönetilmelidir. Bu rehber, işaretçilerin kullanımından dinamik bellek tahsisine, yaygın hatalardan modern C++ akıllı işaretçilerine kadar geniş bir yelpazede bilgi sunmaktadır.
İşaretçi Temelleri: Bellek Adresleriyle Dans
İşaretçileri anlamak için öncelikle bir değişkenin bellekte nasıl saklandığını kavramak gerekir. Her değişkenin bellekte benzersiz bir adresi vardır. İşaretçiler işte bu adresleri tutar.
İşaretçi Tanımlama ve Başlatma: İşaretçi tanımlamak için veri tipinin önüne bir yıldız (*) konulur. Bir değişkene ait bellek adresini almak için ise adres operatörü (&) kullanılır.
Yukarıdaki örnekte `*ptr` ifadesi, `ptr`'nin gösterdiği adresteki değeri ifade ederken, `ptr`'nin kendisi o adresin değeridir. Bu ayrım, işaretçilerle çalışırken temel bir kavramdır.
Dereference Operatörü (*): Bir işaretçinin gösterdiği adresteki değere erişmek için kullanılır. Buna işaretçinin referansını kaldırma (dereferencing) denir.
Adres Operatörü (&): Bir değişkenin bellek adresini almak için kullanılır. Bu operatör, işaretçilere bir başlangıç adresi atamak için kritik öneme sahiptir.
İşaretçi Aritmetiği: Bellekte Gezinme
İşaretçilere tam sayı ekleyebilir veya çıkarabilirsiniz. Ancak bu işlem, doğrudan bayt bazında bir ilerleme anlamına gelmez; işaretçinin veri tipinin boyutu kadar ilerler. Örneğin, bir `int` işaretçisine 1 eklemek, bellek adresini `sizeof(int)` bayt kadar ileri götürür.
Bu özellik, diziler üzerinde etkili bir şekilde gezinmek için kullanılır ve dizilerin işaretçilerle olan güçlü ilişkisini gösterir.
İşaretçiler ve Diziler: Yakın İlişki
C ve C++'ta dizi adları aslında dizinin ilk elemanının adresini tutan sabit işaretçiler gibi davranır. Bu, `dizi` ifadesinin `*(dizi + i)` ile eşdeğer olduğu anlamına gelir.
Bu yakın ilişki, fonksiyonlara dizi geçirme veya karmaşık veri yapılarını yönetme gibi birçok senaryoda avantaj sağlar.
Fonksiyonlara İşaretçi Geçirme (Referansla Çağırma)
C ve C++'ta varsayılan olarak fonksiyonlara parametreler değerle (pass-by-value) geçirilir. Bu, fonksiyon içinde yapılan değişikliklerin orijinal değişkeni etkilemediği anlamına gelir. Ancak işaretçiler kullanarak, değişkenin adresini fonksiyona geçirebilir ve böylece fonksiyonun orijinal değişkenin değerini değiştirmesini sağlayabilirsiniz. Buna referansla çağırma (pass-by-reference) denir.
Bu yöntem, birden fazla değeri döndürmesi gereken fonksiyonlar veya büyük veri yapılarını kopyalamadan işlemek için oldukça kullanışlıdır.
Dinamik Bellek Yönetimi: Çalışma Zamanı Esnekliği
Programın ne kadar belleğe ihtiyacı olacağını derleme zamanında bilemiyorsak (örneğin, kullanıcıdan alınacak bir dizi boyutu), dinamik bellek yönetimi devreye girer. Bu sayede program çalıştıkça gerektiği kadar bellek talep edilebilir ve işi bittiğinde serbest bırakılabilir.
C Dilinde Dinamik Bellek Yönetimi (Heap):
* malloc (memory allocation): Belirtilen boyutta (bayt cinsinden) bir bellek bloğu tahsis eder ve bu bloğun ilk baytının adresini `void*` türünde döndürür. Tahsis edilen bellek alanı başlatılmaz (içinde rastgele değerler olabilir).
* calloc (contiguous allocation): Belirtilen sayıda eleman ve her elemanın boyutuyla bellek tahsis eder. Tahsis edilen tüm baytları sıfırlarla başlatır.
* realloc (re-allocation): Daha önce tahsis edilmiş bir bellek bloğunun boyutunu değiştirmek için kullanılır. Blok büyütülürse, yeni eklenen kısım başlatılmaz.
* free: `malloc`, `calloc` veya `realloc` ile tahsis edilmiş belleği işletim sistemine geri verir. Bu işlem mutlaka yapılmalıdır.
C++ Dilinde Dinamik Bellek Yönetimi (Heap):
C++'ta dinamik bellek yönetimi için `new` ve `delete` operatörleri kullanılır. Bu operatörler, C'deki karşılıklarına göre daha tip güvenlidir ve nesnelerin yapıcılarını (constructor) ve yıkıcılarını (destructor) otomatik olarak çağırır.
* new: Tek bir nesne veya dizi için bellek tahsis eder. Nesnenin yapıcısını çağırır. Bellek tahsisi başarısız olursa `std::bad_alloc` istisnası fırlatır.
* delete: `new` ile tahsis edilmiş tek bir nesnenin belleğini serbest bırakır ve nesnenin yıkıcısını çağırır.
* delete[]: `new[]` ile tahsis edilmiş bir dizinin belleğini serbest bırakır ve dizideki tüm elemanların yıkıcılarını çağırır.
Yaygın Hatalar ve Güvenli Kullanım Metotları
İşaretçiler ve dinamik bellek yönetimi güçlü araçlar olsa da, yanlış kullanıldıklarında ciddi hatalara yol açabilirler. Bu hataları anlamak ve önlemek, sağlam ve güvenilir programlar yazmak için kritik öneme sahiptir.
Bellek Sızıntıları (Memory Leaks): Programın dinamik olarak bellek tahsis etmesine rağmen, işi bittiğinde bu belleği `free` veya `delete` ile serbest bırakmaması durumunda ortaya çıkar. Bu, programın zamanla daha fazla bellek tüketmesine ve sonunda sistem kaynaklarını tüketerek çökmesine neden olabilir.
Dangling Pointers (Askıda Kalan İşaretçiler): Bir işaretçinin, gösterdiği bellek alanının serbest bırakılmasından sonra bile hala o adresi göstermeye devam etmesidir. Bu işaretçi kullanıldığında, tanımsız davranışa (undefined behavior) yol açar, çünkü gösterdiği bellek alanı artık geçerli olmayabilir veya başka bir amaçla kullanılıyor olabilir. Bu riski azaltmak için, `free(ptr);` veya `delete ptr;` işleminden sonra işaretçiyi hemen `NULL` veya `nullptr`'a atamak iyi bir pratiktir (`ptr = NULL;`).
Wild Pointers (Vahşi İşaretçiler): Başlatılmamış işaretçilerdir. Bu işaretçiler rastgele bir bellek adresini tutar ve dereferans edilmeye çalışıldığında programın çökmesine veya veri bozulmasına neden olabilir.
Null Pointer Dereference: `NULL` veya `nullptr` değerine sahip bir işaretçinin gösterdiği değere erişmeye çalışmaktır. Bu da genellikle programın çökmesiyle sonuçlanır. Bellek tahsisinden sonra işaretçinin `NULL` olup olmadığını kontrol etmek bu hatayı önlemek için önemlidir.
Akıllı İşaretçiler (Smart Pointers) - C++11 ve Sonrası
C++11 standardıyla birlikte gelen akıllı işaretçiler, dinamik bellek yönetimini büyük ölçüde basitleştirir ve bellek sızıntıları ile dangling pointer sorunlarını otomatik olarak çözmeye yardımcı olur. RAII (Resource Acquisition Is Initialization) prensibini kullanarak, işaretçinin kapsam dışına çıktığında belleği otomatik olarak serbest bırakırlar. Üç ana türü vardır:
Akıllı işaretçiler, modern C++ programlamada bellek yönetimi için tercih edilen yöntemdir ve bellek güvenliği konusunda önemli iyileştirmeler sunar.
Önemli Kaynaklar ve İleri Okuma
İşaretçiler ve dinamik bellek yönetimi hakkında daha derinlemesine bilgi edinmek için aşağıdaki kaynakları inceleyebilirsiniz:
* Cppreference - Pointers
* Tutorialspoint - C Pointers
* GeeksforGeeks - Pointers
*
(Bellek adresleri ve işaretçilerin görselleştirilmesi, temsili görsel)
Sonuç
İşaretçiler ve dinamik bellek yönetimi, C ve C++ programlamanın temel taşlarıdır. Programcılara belleğin üzerinde doğrudan kontrol imkanı sunarak, karmaşık veri yapıları oluşturma, fonksiyonlara referansla parametre geçirme ve kaynakları çalışma zamanında esnek bir şekilde yönetme yeteneği kazandırırlar. Ancak bu güç, büyük bir sorumlulukla birlikte gelir. Bellek sızıntıları, dangling pointer'lar ve diğer bellekle ilgili hatalar, dikkatli olunmadığında programın istikrarsız olmasına yol açabilir. Modern C++'daki akıllı işaretçiler gibi araçlar bu riskleri azaltmaya yardımcı olsa da, altta yatan kavramları anlamak her programcı için vazgeçilmezdir. Bu rehberin, işaretçiler ve dinamik bellek dünyasına adım atmanızda size kapsamlı bir başlangıç noktası sunmasını umuyoruz. İyi programlamalar!
Programlamada, özellikle C ve C++ gibi düşük seviyeli dillerde, bellek yönetimi hayati bir konudur. Bu noktada işaretçiler ve dinamik bellek tahsisi kavramları öne çıkar. İşaretçiler, değişkenlerin bellek adreslerini tutan özel değişkenlerdir. Bu sayede programcılar, belleğe doğrudan erişebilir, veri yapılarını daha esnek bir şekilde yönetebilir ve bazı durumlarda program performansını optimize edebilirler. Dinamik bellek yönetimi ise, programın çalışma zamanında ihtiyaca göre bellek ayırma ve serbest bırakma yeteneğidir. Statik veya otomatik olarak tahsis edilen belleğin aksine, dinamik bellek (heap) programcı tarafından açıkça yönetilmelidir. Bu rehber, işaretçilerin kullanımından dinamik bellek tahsisine, yaygın hatalardan modern C++ akıllı işaretçilerine kadar geniş bir yelpazede bilgi sunmaktadır.
İşaretçi Temelleri: Bellek Adresleriyle Dans
İşaretçileri anlamak için öncelikle bir değişkenin bellekte nasıl saklandığını kavramak gerekir. Her değişkenin bellekte benzersiz bir adresi vardır. İşaretçiler işte bu adresleri tutar.
İşaretçi Tanımlama ve Başlatma: İşaretçi tanımlamak için veri tipinin önüne bir yıldız (*) konulur. Bir değişkene ait bellek adresini almak için ise adres operatörü (&) kullanılır.
Kod:
#include <stdio.h>
int main() {
int sayi = 10;
int *ptr; // 'ptr' adında bir tamsayı işaretçisi tanımlama
ptr = &sayi; // 'sayi' değişkeninin bellek adresini 'ptr'ye atama
printf("\nDeğişken 'sayi'nin değeri: %d", sayi); // 10
printf("\nDeğişken 'sayi'nin bellek adresi: %p", &sayi);
printf("\nİşaretçi 'ptr'nin tuttuğu adres: %p", ptr);
printf("\nİşaretçi 'ptr'nin gösterdiği değer: %d", *ptr); // * ile adresin gösterdiği değeri okuma
*ptr = 20; // İşaretçinin gösterdiği adresteki değeri değiştirme
printf("\nİşaretçi ile değiştirildikten sonra 'sayi'nin değeri: %d\n", sayi); // 20
return 0;
}
Yukarıdaki örnekte `*ptr` ifadesi, `ptr`'nin gösterdiği adresteki değeri ifade ederken, `ptr`'nin kendisi o adresin değeridir. Bu ayrım, işaretçilerle çalışırken temel bir kavramdır.
Dereference Operatörü (*): Bir işaretçinin gösterdiği adresteki değere erişmek için kullanılır. Buna işaretçinin referansını kaldırma (dereferencing) denir.
Adres Operatörü (&): Bir değişkenin bellek adresini almak için kullanılır. Bu operatör, işaretçilere bir başlangıç adresi atamak için kritik öneme sahiptir.
İşaretçi Aritmetiği: Bellekte Gezinme
İşaretçilere tam sayı ekleyebilir veya çıkarabilirsiniz. Ancak bu işlem, doğrudan bayt bazında bir ilerleme anlamına gelmez; işaretçinin veri tipinin boyutu kadar ilerler. Örneğin, bir `int` işaretçisine 1 eklemek, bellek adresini `sizeof(int)` bayt kadar ileri götürür.
Kod:
#include <stdio.h>
int main() {
int dizi[] = {10, 20, 30, 40, 50};
int *p = dizi; // Dizi adını işaretçiye atama (ilk elemanın adresi)
printf("\nİlk eleman (p): %d", *p); // Çıktı: 10
printf("\nİlk elemanın adresi (p): %p", p);
p++; // İşaretçiyi bir sonraki tamsayıya ilerlet
printf("\nİkinci eleman (p++): %d", *p); // Çıktı: 20
printf("\nİkinci elemanın adresi (p++): %p", p);
p += 2; // İşaretçiyi iki tamsayı daha ileri götür
printf("\nDördüncü eleman (p+=2): %d", *p); // Çıktı: 40
printf("\nDördüncü elemanın adresi (p+=2): %p\n", p);
return 0;
}
Bu özellik, diziler üzerinde etkili bir şekilde gezinmek için kullanılır ve dizilerin işaretçilerle olan güçlü ilişkisini gösterir.
İşaretçiler ve Diziler: Yakın İlişki
C ve C++'ta dizi adları aslında dizinin ilk elemanının adresini tutan sabit işaretçiler gibi davranır. Bu, `dizi` ifadesinin `*(dizi + i)` ile eşdeğer olduğu anlamına gelir.
Kod:
#include <stdio.h>
int main() {
int sayilar[] = {10, 20, 30, 40, 50};
printf("\nSayilar[0]: %d", sayilar[0]);
printf("*(sayilar + 0): %d\n", *(sayilar + 0));
printf("Sayilar[2]: %d", sayilar[2]);
printf("*(sayilar + 2): %d\n", *(sayilar + 2));
// İşaretçi ile dizi elemanlarına erişim
int *ptr_dizi = sayilar; // ptr_dizi = &sayilar[0]
printf("\nİşaretçi ile Sayilar[3]: %d\n", *(ptr_dizi + 3)); // 40
return 0;
}
Bu yakın ilişki, fonksiyonlara dizi geçirme veya karmaşık veri yapılarını yönetme gibi birçok senaryoda avantaj sağlar.
Fonksiyonlara İşaretçi Geçirme (Referansla Çağırma)
C ve C++'ta varsayılan olarak fonksiyonlara parametreler değerle (pass-by-value) geçirilir. Bu, fonksiyon içinde yapılan değişikliklerin orijinal değişkeni etkilemediği anlamına gelir. Ancak işaretçiler kullanarak, değişkenin adresini fonksiyona geçirebilir ve böylece fonksiyonun orijinal değişkenin değerini değiştirmesini sağlayabilirsiniz. Buna referansla çağırma (pass-by-reference) denir.
Kod:
#include <stdio.h>
void degeriDegistir(int *x) {
printf("\nFonksiyon içinde adres: %p", x);
*x = 100; // x'in gösterdiği adresteki değeri 100 yap
}
int main() {
int sayi = 50;
printf("\nFonksiyon öncesi sayi: %d", sayi); // 50
printf("\nmain içinde sayi'nin adresi: %p\n", &sayi);
degeriDegistir(&sayi); // 'sayi' değişkeninin adresini fonksiyona gönder
printf("\nFonksiyon sonrası sayi: %d\n", sayi); // 100
return 0;
}
Bu yöntem, birden fazla değeri döndürmesi gereken fonksiyonlar veya büyük veri yapılarını kopyalamadan işlemek için oldukça kullanışlıdır.
Dinamik Bellek Yönetimi: Çalışma Zamanı Esnekliği
Programın ne kadar belleğe ihtiyacı olacağını derleme zamanında bilemiyorsak (örneğin, kullanıcıdan alınacak bir dizi boyutu), dinamik bellek yönetimi devreye girer. Bu sayede program çalıştıkça gerektiği kadar bellek talep edilebilir ve işi bittiğinde serbest bırakılabilir.
C Dilinde Dinamik Bellek Yönetimi (Heap):
* malloc (memory allocation): Belirtilen boyutta (bayt cinsinden) bir bellek bloğu tahsis eder ve bu bloğun ilk baytının adresini `void*` türünde döndürür. Tahsis edilen bellek alanı başlatılmaz (içinde rastgele değerler olabilir).
* calloc (contiguous allocation): Belirtilen sayıda eleman ve her elemanın boyutuyla bellek tahsis eder. Tahsis edilen tüm baytları sıfırlarla başlatır.
* realloc (re-allocation): Daha önce tahsis edilmiş bir bellek bloğunun boyutunu değiştirmek için kullanılır. Blok büyütülürse, yeni eklenen kısım başlatılmaz.
* free: `malloc`, `calloc` veya `realloc` ile tahsis edilmiş belleği işletim sistemine geri verir. Bu işlem mutlaka yapılmalıdır.
Kod:
#include <stdio.h>
#include <stdlib.h>
int main() {
int *arr;
int n; // Dizi boyutu
printf("\nKaç adet tamsayı depolamak istersiniz? ");
scanf("%d", &n);
// malloc ile n adet int için bellek ayırma
arr = (int*) malloc(n * sizeof(int)); // int* türüne cast etmeyi unutmayın
// Bellek tahsisinin başarılı olup olmadığını kontrol edin
if (arr == NULL) {
printf("Bellek tahsisi başarısız!\n");
return 1; // Hata kodu döndür
}
printf("Değerleri giriniz:\n");
for (int i = 0; i < n; i++) {
printf("Eleman %d: ", i + 1);
scanf("%d", &arr[i]);
}
printf("Girilen değerler:\n");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
// Tahsis edilen belleği serbest bırakma
free(arr);
printf("Bellek serbest bırakıldı.\n");
return 0;
}
C++ Dilinde Dinamik Bellek Yönetimi (Heap):
C++'ta dinamik bellek yönetimi için `new` ve `delete` operatörleri kullanılır. Bu operatörler, C'deki karşılıklarına göre daha tip güvenlidir ve nesnelerin yapıcılarını (constructor) ve yıkıcılarını (destructor) otomatik olarak çağırır.
* new: Tek bir nesne veya dizi için bellek tahsis eder. Nesnenin yapıcısını çağırır. Bellek tahsisi başarısız olursa `std::bad_alloc` istisnası fırlatır.
* delete: `new` ile tahsis edilmiş tek bir nesnenin belleğini serbest bırakır ve nesnenin yıkıcısını çağırır.
* delete[]: `new[]` ile tahsis edilmiş bir dizinin belleğini serbest bırakır ve dizideki tüm elemanların yıkıcılarını çağırır.
Kod:
#include <iostream>
int main() {
// Tek bir tamsayı için bellek tahsisi
int *sayiPtr = new int;
*sayiPtr = 42;
std::cout << "Tek int değeri: " << *sayiPtr << std::endl;
delete sayiPtr; // Belleği serbest bırak
sayiPtr = nullptr; // Dangling pointer'ı önlemek için null'a ata
// 5 adet tamsayıdan oluşan bir dizi için bellek tahsisi
int *diziPtr = new int[5];
for (int i = 0; i < 5; ++i) {
diziPtr[i] = (i + 1) * 10;
}
std::cout << "Dizi elemanları: ";
for (int i = 0; i < 5; ++i) {
std::cout << diziPtr[i] << " ";
}
std::cout << std::endl;
delete[] diziPtr; // Dizi belleğini serbest bırak
diziPtr = nullptr; // Dangling pointer'ı önlemek için null'a ata
return 0;
}
Yaygın Hatalar ve Güvenli Kullanım Metotları
İşaretçiler ve dinamik bellek yönetimi güçlü araçlar olsa da, yanlış kullanıldıklarında ciddi hatalara yol açabilirler. Bu hataları anlamak ve önlemek, sağlam ve güvenilir programlar yazmak için kritik öneme sahiptir.
Bellek Sızıntıları (Memory Leaks): Programın dinamik olarak bellek tahsis etmesine rağmen, işi bittiğinde bu belleği `free` veya `delete` ile serbest bırakmaması durumunda ortaya çıkar. Bu, programın zamanla daha fazla bellek tüketmesine ve sonunda sistem kaynaklarını tüketerek çökmesine neden olabilir.
Önemli Uyarı: Her malloc, calloc, realloc veya new çağrısı için karşılık gelen bir free veya delete/delete[] çağrısı mutlaka yapılmalıdır. Aksi takdirde, programınızda bellek sızıntıları oluşur ve bu durum uzun vadede performans düşüşüne veya program çökmesine yol açabilir.
Dangling Pointers (Askıda Kalan İşaretçiler): Bir işaretçinin, gösterdiği bellek alanının serbest bırakılmasından sonra bile hala o adresi göstermeye devam etmesidir. Bu işaretçi kullanıldığında, tanımsız davranışa (undefined behavior) yol açar, çünkü gösterdiği bellek alanı artık geçerli olmayabilir veya başka bir amaçla kullanılıyor olabilir. Bu riski azaltmak için, `free(ptr);` veya `delete ptr;` işleminden sonra işaretçiyi hemen `NULL` veya `nullptr`'a atamak iyi bir pratiktir (`ptr = NULL;`).
Wild Pointers (Vahşi İşaretçiler): Başlatılmamış işaretçilerdir. Bu işaretçiler rastgele bir bellek adresini tutar ve dereferans edilmeye çalışıldığında programın çökmesine veya veri bozulmasına neden olabilir.
Null Pointer Dereference: `NULL` veya `nullptr` değerine sahip bir işaretçinin gösterdiği değere erişmeye çalışmaktır. Bu da genellikle programın çökmesiyle sonuçlanır. Bellek tahsisinden sonra işaretçinin `NULL` olup olmadığını kontrol etmek bu hatayı önlemek için önemlidir.
Kod:
#include <iostream>
int main() {
int *ptr = nullptr; // İşaretçiyi başlat
// ptr = new int; // Önce bellek tahsis etmeliyiz
if (ptr != nullptr) {
*ptr = 10; // Sadece NULL değilse kullan
std::cout << *ptr << std::endl;
delete ptr;
} else {
std::cout << "Hata: İşaretçi NULL, belleğe erişilemez." << std::endl;
}
return 0;
}
Akıllı İşaretçiler (Smart Pointers) - C++11 ve Sonrası
C++11 standardıyla birlikte gelen akıllı işaretçiler, dinamik bellek yönetimini büyük ölçüde basitleştirir ve bellek sızıntıları ile dangling pointer sorunlarını otomatik olarak çözmeye yardımcı olur. RAII (Resource Acquisition Is Initialization) prensibini kullanarak, işaretçinin kapsam dışına çıktığında belleği otomatik olarak serbest bırakırlar. Üç ana türü vardır:
- std::unique_ptr: Tek sahipliği garanti eder. Bir dinamik bellek alanı için yalnızca bir `unique_ptr` olabilir. `unique_ptr` kopyalanamaz ancak `std::move` ile sahiplik aktarılabilir.
- std::shared_ptr: Kaynak üzerinde birden fazla işaretçinin sahipliğini paylaşmasına izin verir. Bir referans sayacı tutar; kaynak, sayacı sıfıra düştüğünde (yani son `shared_ptr` kopyası yok edildiğinde) otomatik olarak serbest bırakılır.
- std::weak_ptr: `shared_ptr` ile birlikte kullanılır ve `shared_ptr`'lar arasındaki döngüsel referansları kırmak için idealdir. Kaynak üzerinde sahiplik sayısına katkıda bulunmaz ve kaynak yok edildiğinde geçerliliğini yitirir.
Kod:
#include <iostream>
#include <memory> // Akıllı işaretçiler için gerekli başlık
int main() {
// std::unique_ptr kullanımı: Tek sahiplik
std::cout << "\n--- std::unique_ptr --- " << std::endl;
std::unique_ptr<int> unique_sayi(new int(100));
std::cout << "unique_sayi değeri: " << *unique_sayi << std::endl;
// std::unique_ptr<int> unique_sayi_kopyasi = unique_sayi; // HATA: unique_ptr kopyalanamaz!
std::unique_ptr<int> unique_sayi_tasi = std::move(unique_sayi); // Sahiplik aktarımı
std::cout << "unique_sayi_tasi değeri: " << *unique_sayi_tasi << std::endl;
if (unique_sayi == nullptr) {
std::cout << "unique_sayi artık nullptr'ı işaret ediyor (taşındı)." << std::endl;
}
// unique_sayi_tasi kapsam dışına çıktığında bellek otomatik serbest kalır.
// std::shared_ptr kullanımı: Paylaşımlı sahiplik
std::cout << "\n--- std::shared_ptr --- " << std::endl;
std::shared_ptr<double> shared_deger1(new double(3.14));
std::cout << "shared_deger1 değeri: " << *shared_deger1
<< ", referans sayısı: " << shared_deger1.use_count() << std::endl; // 1
std::shared_ptr<double> shared_deger2 = shared_deger1; // Kopyalama, sahiplik paylaşımı
std::cout << "shared_deger2 değeri: " << *shared_deger2
<< ", referans sayısı (shared_deger1): " << shared_deger1.use_count() << std::endl; // 2
std::shared_ptr<double> shared_deger3(shared_deger1); // Başka bir kopyalama
std::cout << "shared_deger3 değeri: " << *shared_deger3
<< ", referans sayısı (shared_deger1): " << shared_deger1.use_count() << std::endl; // 3
shared_deger1.reset(); // shared_deger1 artık kaynağı göstermiyor
std::cout << "shared_deger1 resetlendi. Referans sayısı (shared_deger2): "
<< shared_deger2.use_count() << std::endl; // 2
// shared_deger2 ve shared_deger3 kapsam dışına çıktığında bellek otomatik serbest kalır.
// std::weak_ptr kullanımı: shared_ptr ile birlikte
std::cout << "\n--- std::weak_ptr --- " << std::endl;
std::shared_ptr<int> sp(new int(200));
std::weak_ptr<int> wp = sp; // weak_ptr sahiplik sayısını artırmaz
if (auto locked_sp = wp.lock()) { // weak_ptr'ı shared_ptr'a dönüştürerek kullan
std::cout << "weak_ptr üzerinden değer: " << *locked_sp << std::endl;
} else {
std::cout << "Kaynak yok edildi." << std::endl;
}
sp.reset(); // shared_ptr'ı sıfırla, kaynak yok edilir (çünkü başka shared_ptr yok)
if (auto locked_sp = wp.lock()) {
std::cout << "weak_ptr üzerinden değer: " << *locked_sp << std::endl;
} else {
std::cout << "Kaynak yok edildi." << std::endl; // Şimdi burası çalışır
}
return 0;
}
Akıllı işaretçiler, modern C++ programlamada bellek yönetimi için tercih edilen yöntemdir ve bellek güvenliği konusunda önemli iyileştirmeler sunar.
Önemli Kaynaklar ve İleri Okuma
İşaretçiler ve dinamik bellek yönetimi hakkında daha derinlemesine bilgi edinmek için aşağıdaki kaynakları inceleyebilirsiniz:
* Cppreference - Pointers
* Tutorialspoint - C Pointers
* GeeksforGeeks - Pointers
*

Sonuç
İşaretçiler ve dinamik bellek yönetimi, C ve C++ programlamanın temel taşlarıdır. Programcılara belleğin üzerinde doğrudan kontrol imkanı sunarak, karmaşık veri yapıları oluşturma, fonksiyonlara referansla parametre geçirme ve kaynakları çalışma zamanında esnek bir şekilde yönetme yeteneği kazandırırlar. Ancak bu güç, büyük bir sorumlulukla birlikte gelir. Bellek sızıntıları, dangling pointer'lar ve diğer bellekle ilgili hatalar, dikkatli olunmadığında programın istikrarsız olmasına yol açabilir. Modern C++'daki akıllı işaretçiler gibi araçlar bu riskleri azaltmaya yardımcı olsa da, altta yatan kavramları anlamak her programcı için vazgeçilmezdir. Bu rehberin, işaretçiler ve dinamik bellek dünyasına adım atmanızda size kapsamlı bir başlangıç noktası sunmasını umuyoruz. İyi programlamalar!