---
title: 'Tanımsız Davranış'
source: 'https://academia.sh/tr/kurslar/c-bellek/tanimsiz-davranis'
course: "C'de Bellek ve İşaretçiler"
language: tr
updated: '2026-09-26T03:02:43+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Tanımsız davranışın dört kaynağını (sarkan işaretçi, bırakılmış bellek, çift bırakma, sınır dışı erişim) sözleşme ihlali olarak tanır'
    - 'Tanımsız davranışın neden var olduğunu derleyicinin varsayım yapma hakkıyla açıklar'
    - 'Her tanımsız kaynağın yerine tanımlı bir karşılığı yazar ve koşturarak doğrular'
---

# Tanımsız Davranış

Sarkan işaretçi okumak, bırakılmış belleği kullanmak, bir bloğu iki kez bırakmak ve dizinin sınırı dışına erişmek dördü de tanımsız davranıştır; hiçbiri koşturulmaz, ders her birini düzyazıda tarif eder ve yanına tanımlı karşılığını koşturarak gösterir.

Önceki ders dört bölgeyi tek eksende topladı: yığıt, statik alan, dizgi değişmezi ve öbek —
hepsinin ortak noktası, ömrünü kimin belirlediğinin kaynakta yazılı olmasıydı. Bu ders o
sözleşmenin **kırıldığı** anı ölçüyor. Sözleşme "bu bloğu kim bırakacak" sorusuna bir yanıt
veriyordu; sözleşme kırılınca ortaya çıkan şey bir hata mesajı değil, **tanımsız davranıştır**
(undefined behavior) — ve tanımsız davranışın en önemli özelliği, ölçülemeyecek olmasıdır.

Bu son cümle bir uyarı değil, dersin sınırını çizen bir tanımdır. Aşağıdaki dört bölüm
tanımsız davranışın dört ayrı kaynağını anlatıyor — sarkan işaretçi, bırakılmış bellek,
çift bırakma, sınır dışı erişim — ama hiçbiri kaynağın kendisini koşturmuyor. Her bölümün
yanında koşan kod, o kaynağın **yerine geçen tanımlı karşılıktır**: sözleşme kırılmadan önce
araya giren bir denetim satırı. Bu ayrım kurs boyunca hep aynıdır — ölçülen şey her zaman
tanımlı davranıştır — ama bu derste özellikle önemlidir, çünkü ölçülmeyen tarafın adı da
düzyazıda açıkça geçiyor.

## Tanımsız Davranış Neden Var

C standardı her ifadenin anlamını tanımlamaz. Bir kısım ifade **tanımlıdır**: `int
toplam = a + b;` her zaman aynı kuralla çalışır. Bir kısmı **uygulamaya bağlıdır**
(implementation-defined): derleyici bir seçim yapar ve o seçimi belgeler. Ama bir kısım
ifade **tanımsızdır**: standart onun ne yapacağını hiç söylemez — derleyici istediği gibi
davranabilir, hatta hiç davranmayabilir.

Bu boşluk bir eksiklik değil, bir tasarım kararıdır. Derleyici, bir programın tanımsız
davranışa **hiç girmediğini varsayarak** optimizasyon yapar. Örneğin bir işaretçinin her
zaman geçerli bir bloğu gösterdiğini varsayabilir, çünkü geçersiz bir bloğu göstermesi zaten
tanımsızdır ve standart bu durumda hiçbir garanti vermez. Bu varsayım sayesinde derleyici
gereksiz denetimleri silebilir, sırayı değiştirebilir, bir değeri yeniden hesaplamak yerine
önbelleğe alabilir. Varsayım yanlış çıkarsa — yani program gerçekten tanımsız davranışa
girerse — üretilen kod hâlâ derlenir ve hâlâ **bir şey** yapar, ama o şeyin ne olduğu
standardın konusu değildir.

Bu varsayımın etkisi derleyici sürümüyle, optimizasyon düzeyiyle ve hatta çevredeki
kodun şekliyle değişir; aynı tanımsız ifade bir derlemede fark edilmeden geçebilir, bir
başkasında programın tamamen farklı bir yol izlemesine yol açabilir. Standart bu iki
sonucu da eşit derecede **geçerli** sayar, çünkü ikisi de "tanımsız" sözünün kapsamındadır.
Bu yüzden tanımsız davranışı bulmanın yolu programı çalıştırıp "hata verdi mi" diye bakmak
değildir — hata vermemesi hiçbir şey kanıtlamaz, yalnız o **belirli** koşumda, o **belirli**
derleyicide bir şey gözlenmediğini gösterir.

Ölçüm çekirdeğinin dört sınıfı arasında bu dersin konusu **sahipsiz** bellektir: sözleşme
kırıldığında ne dil, ne çağıran, ne de çağrılan artık o bloğa tanımlı bir yoldan
erişemiyordur — blok hâlâ orada durabilir, ama onu güvenle kullanacak hiçbir taraf kalmaz.
Bu yüzden aşağıdaki dört kaynağın hiçbiri "kim sahipleniyor" sorusuna doğrudan bir yanıt
vermez; hepsi, sahiplik sözleşmesi zaten çiğnendikten **sonraki** anı gösterir.

Bu yüzden "çalışıyor görünmesi" hiçbir kanıt taşımaz. Bir sonraki bölüm, bu depoda ölçüm
çekirdeğinin dört bilerek koşturmadığı kaynağı tek tek ele alıyor; her birinde önce sözleşme
neden kırıldığı düzyazıyla anlatılıyor, sonra yanına **tanımlı** bir karşılığı koşturuluyor.

## Sarkan İşaretçiyi Okumak

Bir blok bırakıldıktan sonra ona işaret eden işaretçi **sarkar** (dangling): işaretçinin
kendisi hâlâ eski adresi tutar, ama o adresteki bellek artık ayırıcıya geri verilmiştir.
Sarkan bir işaretçiyi dolaylamak (`*p`) — okumak amacıyla bile olsa — tanımsız davranıştır.
Bellek fiilen hâlâ aynı içeriği taşıyor gibi görünebilir, çünkü ayırıcı o bloğu henüz başka
bir isteğe vermemiş olabilir; bu **rastlantıdır**, garanti değildir. Aynı program bir sonraki
koşumda, ya da aynı koşumda bir sonraki ayırma isteğinden sonra, o adresi tamamen farklı bir
bloğa verebilir.

Ders bu okumayı hiç koşturmuyor. Yerine, sarkan işaretçinin **oluşmasını engelleyen** tanımlı
kalıbı koşturuyor: bir blok bırakıldığı anda işaretçiye `NULL` atamak ve her kullanımdan önce
bunu denetlemek.

```c
// koru.c — sarkan isaretcinin olusmasini engelleyen tanimli karsilik
#include <stdlib.h>

const char *sarkan_karsiligi(void) {
    int *blok = malloc(sizeof *blok);
    if (blok != NULL) {
        *blok = 9;
    }
    free(blok);
    blok = NULL;
    return (blok == NULL) ? "isaretci bos, dolaylama yapilmadi" : "erisildi";
}
```

Bu karşılığın ölçümü, dersin tek `main`'ini içeren son kod bloğunda, öbür üç karşılıkla
birlikte gösteriliyor.

Dikkat edilmesi gereken nokta şu: `blok`'u `NULL`'a çekmek, işaretçinin **kendisini**
değiştirir, işaret ettiği belleği değil — o bellek `free` çağrısıyla zaten ayırıcıya geri
döndü. Denetim yalnız aynı işaretçi değişkeninin bir daha kullanılmasını engelliyor; aynı
adresi tutan **başka** bir işaretçi varsa (örneğin aynı bloğa işaret eden ikinci bir
değişken), o ikinci işaretçi hâlâ sarkar ve bu kalıp onu korumaz. Kalıbın kapsamı, tam
olarak denetlediği işaretçi değişkeniyle sınırlıdır.

## Bırakılmış Belleği Kullanmak

Bırakılmış belleği kullanmak (use-after-free), sarkan işaretçiyi okumaktan biraz daha
geniştir: bloğa **yazmayı** da kapsar. `free` çağrısından sonra aynı bloğa `*p = değer;`
yazmak, ayırıcının o bellek bölgesini yeniden kullanıma açmış olabileceği anlamına gelir —
yazma, artık ayırıcının kendi defterine ait olan baytların üzerine gidebilir ve ayırıcının
iç tutarlılığını bozabilir. Bu da tanımsızdır ve bir hata iletisi vermeyebilir; program bir
süre daha "normal" çalışabilir.

Tanımlı karşılığı, blokla ilişkilendirilmiş bir **geçerlilik bayrağı** taşımaktır: blok
bırakıldığında bayrak düşürülür ve her yazma bu bayrağa bakar.

```c
// gecerlilik.c — birakma sonrasi yazmayi bayrakla engelleyen kalip
#include <stdlib.h>

const char *kullanim_karsiligi(void) {
    int *ikinci = malloc(sizeof *ikinci);
    int gecerli = (ikinci != NULL);
    if (gecerli) {
        *ikinci = 4;
    }
    free(ikinci);
    ikinci = NULL;
    gecerli = 0;
    if (gecerli) {
        *ikinci = 7;
    }
    return gecerli ? "yazildi" : "yazilmadi, bayrak dustu";
}
```

Bu karşılığın ölçümü de aynı son kod bloğunda gösteriliyor. Bayrağın yanında işaretçinin
kendisi de `free`'den hemen sonra `NULL`'a çekiliyor. Bu satır süs değildir: o olmadan, uyarıları
açık bir derleme `ikinci`'nin `free` sonrasında hâlâ metinde geçtiğini görüp uyarı basar — dal
hiç çalışmasa bile, çünkü derleyici dalın çalışmayacağını her zaman kanıtlayamaz. Derleyicinin
kendisi bile bırakılmış bir işaretçiyi gözünde tutmaya devam eder; bayrak dalı kapatır,
`NULL`'a çekme adın bırakılmış bir adres taşımasını önler ve ikisi birlikte bloğu
uyarısız derletir.

Bayrağın yeri de önemlidir: bayrak, koruduğu işaretçiyle **aynı ömre** sahip olmalıdır.
Bayrak yerel bir değişkense ve işaretçi bir işlevden diğerine geçiyorsa, bayrak işaretçiyle
birlikte taşınmadığı sürece çağrılan taraf işaretçinin hâlâ geçerli olup olmadığını bilemez
— bu da geçerlilik denetiminin kendisinin, denetlediği bloktan **ayrı bir sahiplik**
taşıdığı anlamına gelir; bayrağı kim güncelleyecek, kimin sorumluluğundadır.

## Çift Bırakma

Aynı bloğu iki kez `free`'ye vermek de tanımsızdır. Ayırıcı, bir bloğu bıraktıktan sonra o
adresi kendi defterine (serbest liste) ekler; ikinci `free` çağrısı aynı adresi bu deftere
ikinci kez eklemeye çalışır ve deftere ait iç yapıyı bozabilir. Sonuç, ayırıcının bir sonraki
`malloc` çağrısında görünür ya da hiç görünmez — ikisi de standardın söylediği bir şey değil.

Tanımlı karşılığı, bir bloğun bırakıldığını da bir bayrakla işaretlemek ve ikinci bırakma
isteğini bu bayrakla **atlamaktır**.

```c
// tekbirak.c — cift birakmayi bayrakla engelleyen kalip
#include <stdlib.h>

const char *tek_birakma(void) {
    int *ucuncu = malloc(sizeof *ucuncu);
    int birakildi = 0;

    if (!birakildi) {
        free(ucuncu);
        birakildi = 1;
    }
    if (!birakildi) {
        free(ucuncu);
        birakildi = 1;
    }
    return birakildi ? "bir kez birakildi" : "birakilmadi";
}
```

İkinci `birakildi` denetimi burada **hiç çalışmayacak** — kod satırı olarak var, ama
`birakildi` bayrağı o noktada zaten doğru olduğu için koşulu hiç sağlamayacak. Bu, dersin
başında kurulan kuralın somut hâlidir: tanımsız yolu gösteren satır kaynakta durabilir, yeter
ki hiç yürütülmesin. Ölçümü yine son kod bloğunda.

Çift bırakma, sahiplik açısından bakıldığında ilginç bir durumdur: blok bırakıldığı anda
sahipliği zaten **hiç kimseye** devretmiştir — ne dil, ne çağıran, ne çağrılan onu artık
sahiplenmez. İkinci `free` çağrısı bu boşluğa yapılan bir çağrıdır; ayırıcı o adresi
tanımaz ya da yanlış tanır, çünkü elinde o adresle ilgili artık **geçerli** bir kayıt
yoktur. Tek-bırakma bayrağı bu boşluğu doldurmuyor, yalnız oraya bir daha girilmesini
engelliyor.

## Sınır Dışı Erişim

Bir dizinin `[0, uzunluk)` aralığının dışına erişmek — `dizi[uzunluk]` ya da daha ötesi —
tanımsızdır. Bellek fiziksel olarak orada bir şey içerebilir, ama o bayt dizinin bir parçası
değildir; derleyici, bir dizi erişiminin her zaman sınırlar içinde kaldığını varsayarak sınır
denetimlerini silebilir. Sınır dışı bir okuma rastgele bir değer, sınır dışı bir yazma başka
bir değişkenin verisini bozabilir.

Tanımlı karşılığı, erişimden önce **istenen indeksi bilinen uzunlukla karşılaştırmaktır**.

```c
// sinirli.c — indeksi uzunlukla karsilastirip erisimi tanimli tutan kalip
const char *sinirli_erisim(int istenen) {
    int dizi[4] = {10, 20, 30, 40};
    int uzunluk = 4;
    if (istenen >= 0 && istenen < uzunluk) {
        (void)dizi[istenen];
        return "erisildi";
    }
    return "reddedildi, sinir disi";
}
```

`istenen < uzunluk` denetimi C'nin kendisinde yoktur — dizi erişimi (`dizi[i]`) dilde her
zaman sessizce izin verir, ne çalışma zamanında ne derleme zamanında bir denetim yapılır.
Denetim tamamen **programcının eklediği** bir satırdır; eklenmezse dizinin ötesine geçen bir
indeks derlenir ve tanımsız davranışa girer.

Bu, öbür üç kaynaktan farklı bir noktayı gösteriyor: sarkan işaretçi ve bırakılmış bellek
bir **zamanlama** sorunudur — blok bir noktada geçerliyken sonra geçersiz olur. Sınır dışı
erişim ise zamanlamadan bağımsızdır; dizi hiç bırakılmamış, hâlâ tamamen geçerli olsa bile
yanlış indeks tanımsız davranışa girer. Ortak nokta, ikisinde de derleyicinin **hiçbir**
çalışma zamanı denetimi eklememesidir — ne zamanlamayı, ne indeksi C'nin kendisi izler.

## Dört Karşılığın Birlikte Koşturulması

Dördü de tanımsız değil, tanımsızlığı **önleyen** kalıplardır; hepsi tek bir `main`'de,
sırayla koşturuluyor.

```c
// olcum.c — dordunun de tanimli karsiligi tek main'de olculuyor
#include <stdio.h>

const char *sarkan_karsiligi(void);
const char *kullanim_karsiligi(void);
const char *tek_birakma(void);
const char *sinirli_erisim(int istenen);

static void gozle(const char *ad, const char *sonuc) {
    printf("%-28s%s\n", ad, sonuc);
}

int main(void) {
    printf("%-28s%s\n", "gozlem", "sonuc");
    gozle("sarkan isaretci", sarkan_karsiligi());
    gozle("birakilmis bellek", kullanim_karsiligi());
    gozle("cift birakma denemesi", tek_birakma());
    gozle("gecerli indeks", sinirli_erisim(2));
    gozle("sinir disi indeks", sinirli_erisim(5));
    return 0;
}
```

```text
gozlem                      sonuc
sarkan isaretci             isaretci bos, dolaylama yapilmadi
birakilmis bellek           yazilmadi, bayrak dustu
cift birakma denemesi       bir kez birakildi
gecerli indeks              erisildi
sinir disi indeks           reddedildi, sinir disi
```

**SL1.** `blok == NULL` denetimi burada sarkan işaretçiyi **önlüyor**, sarkan işaretçinin
kendisini ölçmüyor. Tanımsız davranışın ölçülebilir bir "sonucu" yoktur; ölçülebilen tek şey,
ona hiç girilmediğinin kanıtıdır — bu yüzden çıktı satırı "dolaylama yapılmadı" diyor, "değer
şu çıktı" demiyor.

**SL2.** Bayrağın kendisi sıradan bir `int`'tir; C'nin ayrı bir "geçerlilik" tipi yoktur.
Güvence, tipten değil, **her yazma noktasının bayrağı denetlemesi disiplininden** gelir —
derleyici bu disiplini zorlamaz, denetlemeyi unutan bir satır sessizce derlenir.

**SL3.** `tek_birakma` içindeki ikinci `birakildi` denetimi koşumda hiç çalışmadı — çıktı
tek bir "bir kez birakildi" satırı verdi, ikinci bir bırakmanın izi yok.

**SL4.** Son iki satır aynı işlevin aynı diziye iki farklı `istenen` değeriyle çağrılışıdır:
geçerli indeks dizinin içinde kalıp erişiliyor, sınır dışı indeks erişilmeden reddediliyor —
ikisi de tanımlı, ikisi de dizinin ötesine hiç dokunmuyor.

Dört satırın da ortak noktası, hiçbirinin bir dilin ya da bir çalışma zamanının garantisine
dayanmamasıdır. `sarkan_karsiligi` işaretçiyi kendi elleriyle `NULL`'a çeker, `kullanim_karsiligi`
bayrağı kendi elleriyle düşürür, `tek_birakma` ikinci bırakmayı kendi elleriyle atlar,
`sinirli_erisim` sınırı kendi elleriyle karşılaştırır — dördü de aynı bloğu **sahiplenen bir
taraf** olmadığı için, kalan tek güvence kaynakta yazılı denetimdir. Bu, önceki dersin dört
sınıfından (dil, çağıran, çağrılan, sahipsiz) hiçbirinin burada devrede olmadığı anlamına
gelmez; tam tersine, **sahipsiz** sınıfının ne anlama geldiğini tam da bu dört örnek
somutlaştırıyor: blok bir noktada geçerliliğini kaybettiğinde, dil de çağıran da çağrılan da
onu artık kendiliğinden korumaz. Koruma varsa, onu koyan kod satırıdır — derleyici değil,
çalışma zamanı kütüphanesi değil, tip sistemi hiç değil. Bu yüzden sahipsiz sınıfının bu
dersteki karşılığı, bir önceki dersteki dil/çağıran/çağrılan sınıflarının **yerini almaz**;
onların hepsinin ortak bir zayıf noktasını, sözleşme kırıldığı andaki durumu, ayrıca ele alır.

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

Tanımsız davranış bir hata mesajı değildir. Yukarıdaki dört karşılık her koşumda birebir aynı
çıktıyı veriyor — ama bu tutarlılık, karşılıklarının **tanımlı olmasından** geliyor, ilgili
tanımsız işlemlerin "aslında zararsız olduğunu" göstermiyor. Bu dersin hiçbir kod bloğu bir
sarkan işaretçiyi dolaylamıyor, bırakılmış bir bloğa yazmıyor, bir bloğu iki kez bırakmıyor ya
da bir dizinin ötesine erişmiyor; hepsi bunları **önleyen** kalıbı koşturuyor. Bir programın
"çalışıyor görünmesi", içinde tanımsız davranış olmadığının kanıtı değildir — derleyici
sürümü, optimizasyon düzeyi ya da bir sonraki ayırma isteği değiştiğinde aynı kaynak farklı
davranabilir.

Bu, dersin ölçüm sınırını da belirliyor: burada koşturulan hiçbir program, tanımsız
davranışın "ne yaptığını" göstermiyor, çünkü öyle bir şey göstermek zaten kavramın
tanımıyla çelişir. Ders yalnız iki şeyi ölçebiliyor — sözleşmenin nerede kırıldığını
düzyazıyla anlatmayı ve kırılmanın yerine geçen tanımlı yolu koşturmayı. Aradaki fark, bir
laboratuvar deneyiyle bir tarif arasındaki farka benzer: deney sonucu gözlemlenebilir,
tarif yalnız izlenirse sonucu garanti eder — izlenmezse ne olacağı tarifin konusu değildir.

## Özet

- Tanımsız davranış, derleyicinin **hiç girilmediğini varsayarak** optimizasyon yaptığı bir
  boşluktur; "çalışıyor görünmek" onun tanımlı olduğunu göstermez.
- Sarkan işaretçiyi okumak, bırakılmış belleği kullanmak, bir bloğu iki kez bırakmak ve dizinin
  sınırı dışına erişmek — dördü de tanımsızdır ve bu derste **hiçbiri koşturulmadı**.
- Her kaynağın tanımlı bir karşılığı vardır: geçerlilik bayrağı, tek-bırakma bayrağı ve
  indeks-uzunluk denetimi — hepsi programcının eklediği, dilin zorlamadığı satırlardır.
- Bu dersin ölçtüğü bellek **sahipsiz** sınıfındadır: sözleşme kırıldığında blok bırakılmakla
  yükümlü hiçbir tarafça artık tanımlı yoldan erişilemez.

## Sonraki Adım

Sözleşmenin kırılması yalnız bellek erişiminde değil, bir işlevin **hata döndürdüğü** yolda da
görülür — orada da bir şeyin temizlenmesi gerekir, ama bu kez tanımsız davranışa girmeden.
Sonraki ders bu temizliği çağrılan sınıfının işlevi üzerinden ölçüyor: dönüş kodu, hata
numarası ve tek çıkış noktalı temizlik kalıpları.
