---
title: 'Denetim Akışı'
source: 'https://academia.sh/tr/kurslar/c-temelleri/denetim-akisi'
course: 'C Temelleri'
language: tr
updated: '2026-09-13T05:07:32+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Bir dalın hangi kod yolunu izleyeceğinin koşumda üretilen veriye bağlı olduğunu ölçer'
    - "switch düşmesini, döngü biçimlerini ve goto'nun sınırlı kullanımını örnekle ayırt eder"
    - 'Hangi dalların var olduğu ile hangisinin koştuğu arasındaki farkı ön işlemci ve koşum üzerinden gösterir'
---

# Denetim Akışı

Bir dalın hangi kod yolunu izleyeceğinin koşumda üretilen veriye bakılarak belirlenmesi; switch düşmesi, döngü biçimleri, goto'nun sınırlı yeri ve hangi dalların var olduğu ile hangisinin seçildiği arasındaki ayrım.

İşleçler dersi, bir ifadenin sonucunu neyin belirlediğini ölçtü ve yanıt hep aynıydı:
kaynakta yazılan tipler ve yapı, koşum başlamadan önce zaten biliniyordu. Denetim akışı
bu düzeni tersine çeviriyor. Bir program hangi dalı izleyecek, hangi döngü kaç kez
dönecek — bunlar artık kaynağı okuyarak önceden söylenemez; ancak program çalışıp veriyi
ürettikten sonra bilinir.

Programlama Temelleri kursu (M01/K02) `if`, `while` ve benzeri denetim yapılarını
Python'la zaten kurmuştu; oradaki sözdizimi ile buradaki arasında yüzeysel bir benzerlik
var, ama bu ders sözdizimini değil, **kararın hangi aşamada verildiğini** ölçüyor: bir
dalın seçilip seçilmediği veriye, hangi dalların kaynakta **var olduğu** ise ayrı bir
aşamaya bağlı olabiliyor.

Bu ders M11'in ölçü ekseni bakımından bir dönüm noktası. Veri Tipleri, Değişkenler ve
İşleçler dersleri hep aynı yöne işaret etti: kaynakta yazılan tipler ve yapı, koşum hiç
başlamadan kararı veriyordu. Denetim akışında bu yön tersine döner — bir dalın seçilip
seçilmeyeceği artık kaynağı okuyarak değil, programı çalıştırıp veriyi üreterek bilinir.
Ama bu tersine dönüş mutlak değil: aşağıda görüleceği gibi, hangi dalların kaynakta bile
**mevcut olduğu** hâlâ derleyiciden önceki bir aşamada, ön işlemcide karara bağlanabiliyor.
Denetim akışı, bu yüzden tek bir sınıfa değil, iki sınıfın kesişimine oturan bir konu.

## Veriye Bağlı Dallanma

Aşağıdaki kesit, Derleme Aşamaları dersinde tanıtılan çekirdeğin bir parçasıdır: bir
dizinin toplamı hesaplanıyor ve bu toplama bakılarak bir dal seçiliyor.

```c
// asama.c — veriye bagli dallanma (kesit)
#include <stdio.h>
#include <stddef.h>

int main(void) {
    int veri[] = {4, 8, 15, 16};
    int toplam = 0;
    for (size_t i = 0; i < sizeof veri / sizeof veri[0]; i++) {
        toplam += veri[i];
    }
    const char *karar = (toplam > 40) ? "buyuk" : "kucuk";
    printf("%s %s\n", "veriye bagli dallanma", karar);
    return 0;
}
```

```
veriye bagli dallanma buyuk
```

**TS7.** `karar` değişkeninin `"buyuk"` mu `"kucuk"` mu olacağı, kaynakta hiçbir yerde
yazılı değil — `veri` dizisindeki dört sayı toplanana kadar bilinemez. `toplam`
`43` çıktığı için `"buyuk"` dalı seçiliyor, ama diziye tek bir eleman eklense ya da bir
eleman değişse sonuç değişebilirdi; kaynak kodun kendisi hiç değişmeden. Bu, önceki
derste ölçülen işleç seçimiyle taban tabana zıt bir karar: orada sonucu tipler
belirliyordu ve koşum başlamadan biliniyordu, burada sonucu veri belirliyor ve ancak
döngü tamamlandıktan sonra bilinebiliyor.

Bu fark `?:` koşullu işlecinin kendisinde de görünür. `(toplam > 40) ? "buyuk" :
"kucuk"` ifadesinin **hangi dalının** değer üreteceği koşuma bağlı, ama ifadenin
**kendisinin bir değer ürettiği** derlemede zaten bellidir — derleyici iki daldan ortak
bir sonuç tipi türetir (burada ikisi de aynı olduğu için `const char *`) ve ikisinden
birinin seçileceğini bilir, yalnız hangisinin seçileceğini bilmez. Bu, denetim akışının
genel bir özelliğini gösteriyor: kaynak, hangi dalların **var olduğunu** ve her dalın
hangi tipte bir sonuç üreteceğini derlemede sabitler; yalnız veriye bakılarak
hangisinin **çalışacağı** koşuma bırakılır. `if` gövdeleri ve `switch` case'leri için de
aynı ayrım geçerli: kaynaktaki her dal derlenir ve ikili dosyada yerini alır, ama bir
koşumda hangisinin gerçekten yürütüldüğü ancak o koşum bittikten sonra söylenebilir.

## Switch Düşmesi

Bir `switch` ifadesinde art arda gelen `case` etiketleri, aralarında `break` olmadıkça
aynı gövdeye **düşer** (fall-through) — bir etiketten bir sonrakine akış kesilmeden
geçilir. **`düşme` burada `fall-through` karşılığıdır ve M06/K05'te kapalıdır** — bu
kullanım o dersteki `drop` duyusundan tamamen ayrı, C'de `drop` kavramı zaten yok.

```c
// gecisli.c — switch dusmesi (fall-through) art arda etiketleri ayni govdeye baglar
#include <stdio.h>

static void seviyeyi_yazdir(int puan) {
    switch (puan / 10) {
        case 10:
        case 9:
            printf("%s\n", "seviye: A");
            break;
        case 8:
        case 7:
            /* kasitli dusme: 8 ve 7, ortak govdeye dusuyor */
            printf("%s\n", "seviye: B");
            break;
        default:
            printf("%s\n", "seviye: C");
            break;
    }
}

int main(void) {
    seviyeyi_yazdir(95);
    seviyeyi_yazdir(88);
    seviyeyi_yazdir(72);
    seviyeyi_yazdir(40);
    return 0;
}
```

```
seviye: A
seviye: B
seviye: B
seviye: C
```

`puan / 10` ifadesi `10` verdiğinde akış gövdesiz `case 10` etiketine giriyor, hiçbir
gövde bulmadan `case 9`a düşüyor ve oradaki gövdeyi çalıştırıyor; `9` verdiğinde ise
doğrudan `case 9`a atlıyor. İki etiket böylece tek bir gövdeyi paylaşıyor. Aynı düzen
`case 8` ile `case 7` için de geçerli. `break`
yazılmadığında akışın devam etmesi C'nin varsayılan davranışı; bir gövdeden çıkmak
isteyen her `case` bunu **açıkça** `break` ile belirtmek zorunda. Yukarıdaki dört çağrı
bunu doğruluyor: `95` gövdeli `case 9`a eşleşiyor ve düşmeye hiç uğramıyor; `88` ise
gövdesiz `case 8`e eşleşip `case 7`nin gövdesine düşüyor.

Düşmenin kendisi, hangi `case`in eşleştiğine bakılmaksızın kaynakta **her zaman
mevcuttur** — derleyici, `break` yazılmayan her etiketten bir sonrakine akan bir yol
üretir ve bu yol ikili dosyada durur. Ama bu yolun **izlenip izlenmeyeceği**, yalnız
`switch` ifadesinin başındaki değere bakılarak koşumda belirlenir: `95` girildiğinde akış
zaten `case 10`dan başlamıyor, doğrudan `case 9`a atlıyor, çünkü `switch` bir eşleşen
etiketi bulduğu anda oraya sıçrar ve yalnız **oradan sonrasını** sırayla yürütür. Düşme,
bu yüzden "her etiket sırayla denenir" anlamına gelmiyor — `switch` önce eşleşen etiketi
buluyor, düşme yalnız o noktadan **sonraki** etiketleri, aralarında `break` yoksa,
gövdesiz geçiyor.

## Döngü Biçimleri: Koşulun Ne Zaman Sınandığı

C üç temel döngü biçimi taşır ve aralarındaki fark, koşulun döngü gövdesinden **önce**
mi **sonra** mı sınandığıdır — bu da veriye bağlı bir davranış farkına yol açar.

```c
// donguler.c — while ve do-while ayni kosulu farkli calisma sayisiyla yorumlar
#include <stdio.h>

int main(void) {
    int sayac = 5;
    int calisma_sayisi_while = 0;
    while (sayac < 5) {
        calisma_sayisi_while++;
        sayac++;
    }
    printf("%s %d\n", "while calisma sayisi", calisma_sayisi_while);

    int calisma_sayisi_dowhile = 0;
    do {
        calisma_sayisi_dowhile++;
    } while (sayac < 5);
    printf("%s %d\n", "do-while calisma sayisi", calisma_sayisi_dowhile);
    return 0;
}
```

```
while calisma sayisi 0
do-while calisma sayisi 1
```

`sayac` baştan `5` ve `while (sayac < 5)` koşulu gövdeden **önce** sınandığı için, koşul
en baştan yanlış ve gövde hiç çalışmıyor: `calisma_sayisi_while` sıfırda kalıyor.
`do { ... } while (sayac < 5)` ise gövdeyi **önce çalıştırıp sonra** koşulu sınıyor;
koşul yine yanlış olsa da gövde en az bir kez çalışmış oluyor ve `calisma_sayisi_dowhile`
`1` çıkıyor. İki döngü de aynı koşulu taşıyor, aynı veriyle çalışıyor, ama sınama sırası
farklı olduğu için sonuçları ayrışıyor — bu fark, koşulun kaynakta nerede yazıldığına
değil, döngünün hangi biçimde bildirildiğine bağlı ve `for` döngüsü de `while` gibi
koşulunu gövdeden önce sınar.

Üç biçimin de gövdesi kaynakta yalnız bir kez yazılır, ama kaç kez **çalışacağı**
koşumdaki veriye bağlıdır — `sayac`ın başlangıç değeri farklı olsaydı, aynı üç döngü
farklı sayıda dönerdi. `while` ile `for` arasındaki fark yalnız yazım biçimidir: `for
(baslangic; kosul; adim)` bir `while` döngüsünün üç parçasını tek satırda toplar ve
ikisi de aynı sınama sırasını (koşul önce) izler. `do-while` ise tek biçimde farklı: bir
döngünün gövdesi koşula bakılmaksızın en az bir kez çalışmalıysa — bir menü göstermek,
bir girdi istemek gibi — bu biçim, ayrı bir bayrak değişkeni ya da gövdeyi iki kez yazmak
zorunda kalmadan bunu doğrudan ifade eder.

## Goto: Sınırlı ve Kabul Edilen Bir Kullanım

`goto`, kaynağın herhangi bir yerinden bir etikete doğrudan atlar ve genel kullanımı
akışı izlemeyi zorlaştırdığı için önerilmez. Ama ic ice geçmiş döngülerden **tek adımda**
çıkmak gibi dar bir senaryoda, `break`in yalnız en yakın döngüyü kırabilmesi yüzünden
`goto` yaygın kabul gören bir çözüm sunar.

```c
// arama.c — goto, ic ice donguden tek adimda cikmanin sinirli ve kabul edilen kullanimi
#include <stdio.h>

int main(void) {
    int izgara[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
    int hedef = 6;
    int bulunan_i = -1, bulunan_j = -1;

    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            if (izgara[i][j] == hedef) {
                bulunan_i = i;
                bulunan_j = j;
                goto bulundu;
            }
        }
    }
bulundu:
    printf("%s %d\n", "bulunan satir", bulunan_i);
    printf("%s %d\n", "bulunan sutun", bulunan_j);
    return 0;
}
```

```
bulunan satir 1
bulunan sutun 2
```

`hedef` değeri `izgara[1][2]`de bulunuyor ve `goto bulundu;` iki döngüyü de tek satırda
terk ediyor. Aynı sonucu `break` ile elde etmek isteyen bir yazım, iç döngüden çıktıktan
sonra dış döngüyü de bir bayrak değişkenle kontrol edip ayrıca kırmak zorunda kalırdı —
`goto` burada ek bir bayrak değişkeni gerektirmeden aynı işi görüyor. `bulundu`
etiketinin döngülerin dışında, işlevin sonuna yakın durması da kabul gören kullanımın bir
parçası: `goto` yalnız ileri doğru ve işlevin doğal akışının bittiği bir noktaya atlıyor,
kaynağın ortasında karmaşık bir sıçrama ağı kurmuyor.

`bulundu` etiketine gerçekten atlanıp atlanmayacağı da veriye bağlı bir karar: `hedef`
değeri `izgara` içinde hiç bulunmasaydı, iki döngü de tamamen bitmiş ve `goto` hiç
çalışmamış olurdu, akış etikete yalnız döngülerin doğal sonunda, sırayla ulaşırdı.
Kaynakta `goto bulundu;` satırı hep aynı yerde duruyor, ama o satırın çalışıp
çalışmayacağı — ve çalışırsa kaçıncı döngü adımında — `izgara`nın içeriğine ve `hedef`in
değerine bakılarak koşumda belirleniyor. `goto` bu yüzden denetim akışının genel
kuralından ayrılmıyor: sıçramanın kendisi kaynakta sabit, sıçramanın **tetiklenip
tetiklenmeyeceği** veriye bağlı.

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

Bir dalın hangi kod yolunu izleyeceği veriye bağlıydı, ama hangi dalların kaynakta **var
olduğu** her zaman koşumla belirlenmez — bazen ön işlemci, derleyici kaynağı görmeden
önce, hangi `case`lerin bile var olacağına karar vermiş olabilir.

```c
// sinir.c — hangi dallarin var oldugu on islemcide, hangisinin secildigi kosumda belirlenir
#include <stdio.h>

#define EK_DURUM

static void kodu_degerlendir(int kod) {
    switch (kod) {
        case 1:
            printf("%s\n", "durum 1 calisti");
            break;
#ifdef EK_DURUM
        case 2:
            printf("%s\n", "durum 2 calisti (on islemci varligini onayladi)");
            break;
#endif
        default:
            printf("%s\n", "taninmayan durum");
            break;
    }
}

int main(void) {
    for (int kod = 1; kod <= 3; kod++) {
        kodu_degerlendir(kod);
    }
    return 0;
}
```

```
durum 1 calisti
durum 2 calisti (on islemci varligini onayladi)
taninmayan durum
```

`EK_DURUM` makrosu tanımlı olduğu için `case 2` etiketi, derleyici kaynağı görmeden
**önce**, ön işlemci tarafından metne dahil ediliyor. `EK_DURUM` tanımlı olmasaydı, o
`case` bloğu kaynaktan tamamen silinmiş olurdu — derleyici onu hiç görmez, `kod == 2`
geldiğinde akış doğrudan `default`a düşerdi. Bu ölçümde `EK_DURUM` tanımlı olduğu için
`case 2` var ve `kod` döngüde `1`den `3`e kadar ilerlerken hangi durumun **seçildiği**
her adımda veriye (döngü sayacına) bakılarak koşumda belirleniyor. İki karar birbirinden
tamamen bağımsız: bir dalın **var olup olmadığı** ön işlemcide, o dalın **seçilip
seçilmediği** koşumda karara bağlanıyor — ve bir dal hiç var olmazsa, hiçbir veri onu
seçemez.

Bu iki aşamanın sırası tersine çevrilemez. Ön işlemci önce çalışır ve `#ifdef` bloklarını
kaynaktan ya tamamen siler ya da tamamen bırakır; derleyici, kendisine ulaşan metinde
zaten hangi `case`lerin bulunduğunu bilir, hiçbir zaman "bu `case` şu veriyle var olsun"
diye bir karar vermez. Koşum ise tam tersi bir sınırlamayla çalışır: elindeki ikili
dosyada zaten hangi dalların bulunduğunu değiştiremez, yalnız var olanlar arasından
seçim yapabilir. Bir hatanın kaynağını ararken bu ayrım pratik bir sonuç doğurur: "bu
dal hiç çalışmadı" ile "bu dal zaten kaynakta yok" iki ayrı olası neden ve ikisinin
denetlendiği yer de farklı — biri koşum çıktısında, öbürü kaynağın ön işlemci
yönergelerinde aranır.

## Özet

- Bir dalın hangi kod yolunu izleyeceği **koşumda** üretilen veriye bakılarak belirlenir;
  kaynak kodun kendisi bu kararı önceden vermez — sınıf **koşum**.
- `switch` içinde `break` olmayan `case` etiketleri bir sonraki gövdeye **düşer**;
  bu düşme, `break` kaynakta açıkça yazılmadıkça C'nin varsayılan davranışıdır.
- `while` koşulu gövdeden önce, `do-while` koşulu gövdeden sonra sınanır; aynı koşul ve
  aynı veriyle bile bu sıra farkı çalışma sayısını değiştirebilir.
- `goto`, iç içe döngülerden tek adımda çıkmak gibi dar ve kabul gören bir senaryoda,
  ek bir bayrak değişkeni gerektirmeden aynı işi görür.
- Hangi dalların kaynakta **var olduğu** ön işlemcide, var olan dallardan **hangisinin
  seçildiği** koşumda karara bağlanabilir — iki karar aynı `switch` içinde bir arada
  bulunabilir ve birbirinden bağımsızdır.
- `?:` koşullu işleci bir değer ürettiğini ve o değerin tipini derlemede sabitler; tip iki
  daldan türetilir, yalnız hangi dalın gerçekten değer üreteceği koşumda belirlenir.

## Sonraki Adım

Bu derste ölçülen dallanmalar hep bilinen bir işlevin gövdesi içinde kaldı — hangi dalın
seçileceği veriye bağlıydı, ama hangi işlevin çağrıldığı hep kaynakta sabitti. Fonksiyonlar
dersi bu son varsayımı sınıyor: bir çağrının hangi tanıma bağlanacağı, iki birim ayrı ayrı
derlendikten sonra, ancak bağlama sırasında çözülüyor. Yukarıdaki `kodu_degerlendir` gibi
bir işlev hep aynı dosyada tanımlıydı; sıradaki derste bir işlev bir birimde çağrılacak,
tanımı ise başka bir birimde duracak — ve aradaki bağı kim, hangi aşamada kurduğunu
belirleyecek. `toplam > 40` gibi bir sınamanın sonucu bu derste yalnız `veri` dizisine
bağlıydı; sıradaki derste bir çağrının sonucu, ayrıca çağrılan işlevin **hangi tanıma**
ulaştığına da bağlı olacak — kaynakta yazılan bir ad, iki farklı birimde iki farklı
gövdeye karşılık gelemez, ama hangi gövdeye bağlandığını yalnız bağlayıcı bilir.
