---
title: 'Fonksiyon İşaretçileri'
source: 'https://academia.sh/tr/kurslar/c-bellek/fonksiyon-isaretcileri'
course: "C'de Bellek ve İşaretçiler"
language: tr
updated: '2026-09-26T03:02:43+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Bir fonksiyon işaretçisini tanımlar ve çağırarak başka bir işlevi dolaylı biçimde çalıştırır'
    - 'Bir işlevi başka bir işleve parametre olarak geçirip bir geri çağrı kurar'
    - 'Bir geri çağrının taşıdığı bağlamın sahipliğinin çağırana ait olduğunu ölçer'
---

# Fonksiyon İşaretçileri

Bir işlevin kendisini gösteren ve başka bir işleve parametre olarak geçirilebilen işaretçiler; bir geri çağrının taşıdığı bağlamın sahipliğinin çağırana ait olduğu.

Şimdiye kadarki beş ders, bir işaretçinin **veriyi** — bir tamsayıyı, bir diziyi, opak bir
kaydı — gösterdiği durumları ölçtü. Bu son ders farklı bir şeyi gösteren bir işaretçiyi
ölçüyor: **işlevin kendisini**. Bir fonksiyon işaretçisi, derlenmiş bir işlevin adresini
tutar ve o işlevi dolaylı biçimde çağırmayı sağlar — bu, bir işlevi çalışma zamanında
seçmenin ya da başka bir işleve **parametre olarak geçirmenin** temelidir.

Bir fonksiyon işaretçisinin bildirimi ilk bakışta karışık görünür: `int (*islem)(int, int)`
ifadesindeki parantezler rastgele değil, zorunlu — `int *islem(int, int)` yazılsaydı bu,
`int *` döndüren ve `islem` adlı bir işlevin **bildirimi** olurdu, `islem` adında bir
işaretçi değil. Parantezler `*islem`'i önce bir isim olarak gruplandırıp derleyiciye
"`islem` bir işaretçidir, gösterdiği şey şu imzaya uyan bir işlevdir" dedirtiyor. Bu
karmaşıklık genellikle bir `typedef` ile gizlenir — nitekim `geri_baglam.c`'deki
`void (*islev)(int, void *)` parametre tipi, `islem_tablosu.c`'deki `IkiliIslem` tanımıyla
aynı deseni izliyor, yalnız birinde tip doğrudan yazılmış, diğerinde bir isme bağlanmış.

## Fonksiyon İşaretçisini Tanımlamak ve Çağırmak

```c
// islev_isaretcisi.c — bir islev isaretcisi, derlenmis bir islevin adresini tutar
#include <stdio.h>

static int topla(int a, int b) {
    return a + b;
}

static int carp(int a, int b) {
    return a * b;
}

int main(void) {
    int (*islem)(int, int) = topla;
    printf("%s %d\n", "topla uzerinden dolayli cagri", islem(3, 4));

    islem = carp;
    printf("%s %d\n", "carp uzerinden dolayli cagri", islem(3, 4));

    printf("%s %d\n", "islem toplaya esit mi", (int)(islem == topla));
    printf("%s %d\n", "islem carpa esit mi", (int)(islem == carp));
    return 0;
}
```

```
topla uzerinden dolayli cagri 7
carp uzerinden dolayli cagri 12
islem toplaya esit mi 0
islem carpa esit mi 1
```

**IS25.** `int (*islem)(int, int)` bildirimi, `islem`'in iki `int` alıp bir `int`
döndüren bir işlevin adresini tutacağını söylüyor. `islem = topla` atamasından sonra
`islem(3, 4)` çağrısı **7** veriyor — `topla`'yı dolaylı biçimde çalıştırıyor. `islem`
daha sonra `carp`'a **yeniden atanıyor** ve aynı çağrı yazımıyla (`islem(3, 4)`) bu kez
**12** veriyor. `islem == topla` **0**, `islem == carp` **1** — bu, fonksiyon
işaretçilerinin de veri işaretçileri gibi eşitlikle karşılaştırılabildiğini gösteriyor;
`islem` şu an hangi işlevi gösteriyorsa onunla eşleşiyor.

## Geri Çağrı: Bir İşlevi Başka Bir İşleve Geçirmek

```c
// geri_cagri.c — bir islevi parametre olarak gecirmek, calisma zamaninda davranisi secer
#include <stdio.h>

static int kare(int x) {
    return x * x;
}

static int negatif(int x) {
    return -x;
}

static void uygula_ve_yazdir(int deger, int (*donusum)(int), const char *etiket) {
    printf("%s %d\n", etiket, donusum(deger));
}

int main(void) {
    uygula_ve_yazdir(6, kare, "kare donusumu");
    uygula_ve_yazdir(6, negatif, "negatif donusumu");
    return 0;
}
```

```
kare donusumu 36
negatif donusumu -6
```

**IS26.** `uygula_ve_yazdir` işlevi, `int (*donusum)(int)` parametresiyle **hangi
işlevin çalıştırılacağını çağıranın belirlemesine** izin veriyor — işlevin kendi içinde
`kare` ya da `negatif` adı hiç geçmiyor. İlk çağrıda `kare` geçiyor ve sonuç **36**
(6*6), ikinci çağrıda `negatif` geçiyor ve sonuç **-6** çıkıyor. Bu düzen, aynı
`uygula_ve_yazdir` gövdesini değiştirmeden farklı davranışlar elde etmeyi sağlıyor —
davranış, derleme zamanında değil çağrı anında **parametre olarak** seçiliyor. Bu
düzene **geri çağrı** deniyor: `donusum`, `uygula_ve_yazdir`'in içinden geriye, çağıranın
sağladığı işleve çağrı yapıyor.

Bu düzenin `topla`/`carp` örneğinden farkı, işlevin **kendisinin** artık bir değişkende
tutulmaması, doğrudan bir parametre olarak akmasıdır. `islem` değişkeni `main` içinde
kalıcıydı ve yeniden atanabiliyordu; `donusum` ise yalnız `uygula_ve_yazdir`'in çalıştığı
süre boyunca var olan geçici bir parametre. İki kullanım da aynı fonksiyon işaretçisi
mekanizmasını paylaşıyor, ama biri **durum tutmayı** (`islem` hangi işlevi gösteriyor),
diğeri **tek seferlik yönlendirmeyi** (bu çağrıda hangi işlev kullanılacak) örnekliyor.

## Bağlamın Sahipliği: Geri Çağrıya Taşınan Veri

Bir geri çağrı çoğu zaman yalnız bir değeri değil, o değere eşlik eden bir **bağlamı**
(context) da taşır. Bu bağlam kimin sahipliğinde olmalı? Aşağıdaki örnek, çağıranın bir
bağlam sağladığı ve bu bağlamın ömrünü kendisinin yönettiği durumu ölçüyor.

```c
// geri_baglam.c — geri cagriya tasinan baglamin sahibi onu saglayan cagirandir
#include <stdio.h>

typedef struct {
    int carpan;
    int toplam;
} Baglam;

static void birikti(int deger, void *baglam) {
    Baglam *b = baglam;
    b->toplam += deger * b->carpan;
}

static void dizi_uzerinde_calistir(const int *dizi, int uzunluk,
                                    void (*islev)(int, void *), void *baglam) {
    for (int i = 0; i < uzunluk; i++) {
        islev(dizi[i], baglam);
    }
}

int main(void) {
    int degerler[4] = {1, 2, 3, 4};
    Baglam b = {.carpan = 3, .toplam = 0};

    dizi_uzerinde_calistir(degerler, 4, birikti, &b);
    printf("%s %d\n", "carpanli toplam", b.toplam);

    b.carpan = 1;
    b.toplam = 0;
    dizi_uzerinde_calistir(degerler, 4, birikti, &b);
    printf("%s %d\n", "carpansiz toplam", b.toplam);
    return 0;
}
```

```
carpanli toplam 30
carpansiz toplam 10
```

**IS27.** `Baglam` yapısı `main` içinde **yerel bir değişken** olarak tanımlanıyor —
`say_ayir` ya da `malloc` ile hiç ayrılmıyor, sınıfı **dil**. Ama `dizi_uzerinde_calistir`
işlevine `&b` olarak geçiyor ve o işlev bunu `birikti`'ye `void *baglam` parametresi
üzerinden **aktarıyor**. `birikti`, `baglam`'ı kendi sahiplenmiyor — yalnız `main`'in
sahip olduğu belleğe yazıyor. İlk koşumda çarpan **3** ve toplam **30** (3*(1+2+3+4)),
ikinci koşumda çarpan **1**'e sıfırlanıyor ve toplam **10** çıkıyor — `b` yeniden
kullanılabiliyor çünkü ömrü hâlâ `main`'e ait. Geri çağrıya geçen bir bağlam, dinamik
olarak ayrılmış olsaydı bile aynı kural geçerli olurdu: bağlamı **sağlayan taraf**
(çağıran) onu bırakmakla yükümlü kalırdı, `birikti` gibi bir geri çağrı işlevi onu asla
kendi sahiplenmez.

Bu düzen, bu kursun ilk dersindeki `opak kayit` gözlemiyle aynı sahiplik biçimini
paylaşıyor ama ters yönden: orada `çağrılan` (`sahip.c`) kendi verisini tutuyor ve
çağırana yalnız bir ödünç veriyordu, burada ise **çağıran** kendi verisini tutuyor ve
`çağrılan` (`birikti`, `dizi_uzerinde_calistir`) bu veriye yalnız bir işaretçi üzerinden
erişiyor. İki durumda da veri tek bir tarafta kalıyor; değişen yalnızca hangi tarafın
belleği tuttuğu. `void *baglam` parametresi burada `05-void-and-opaque-pointers.mdx`
dersindeki `void *`'la aynı kuralı izliyor: `birikti` işlevi `baglam`'ı önce `Baglam *`
tipine dönüştürüyor, ancak ondan sonra alanlarına erişiyor — tip bilgisi çağıran
tarafından taşınıyor, geri çağrı işlevi bunu yalnız geri kazanıyor.

## Fonksiyon İşaretçisi Dizisi: Bir Dağıtım Tablosu

Bir fonksiyon işaretçisi tek başına yeniden atanabildiği gibi, bir **dizi** içinde de
tutulabilir — bu, çalışma zamanında hangi işlevin çalışacağını bir indeksle seçmeyi
sağlar.

```c
// islem_tablosu.c — bir islev isaretcisi dizisi, calisma zamaninda dagitim tablosu kurar
#include <stdio.h>

typedef int (*IkiliIslem)(int, int);

static int topla(int a, int b) { return a + b; }
static int cikar(int a, int b) { return a - b; }
static int carp(int a, int b) { return a * b; }

int main(void) {
    IkiliIslem tablo[3] = { topla, cikar, carp };
    const char *adlar[3] = { "topla", "cikar", "carp" };
    int sonuclar[3];

    for (int i = 0; i < 3; i++) {
        sonuclar[i] = tablo[i](10, 4);
        printf("%s %d\n", adlar[i], sonuclar[i]);
    }

    int toplam = 0;
    for (int i = 0; i < 3; i++) {
        toplam += sonuclar[i];
    }
    printf("%s %d\n", "sonuclarin toplami", toplam);
    return 0;
}
```

```
topla 14
cikar 6
carp 40
sonuclarin toplami 60
```

Bu tablo, `if`/`else if` zincirinin ya da bir `switch` ifadesinin yerini alan bir dağıtım
biçimidir: yeni bir işlem eklemek `tablo` ve `adlar` dizilerine bir eleman eklemekle
sınırlı kalır, çağıran kodun kendisi değişmez.

**IS28.** `tablo` dizisinin her elemanı farklı bir işlevin adresini tutuyor;
`tablo[i](10, 4)` çağrısı hangi işlevin koşacağını **çalışma zamanında, `i` indeksine
göre** seçiyor — kaynak kodunda `topla(10, 4)`, `cikar(10, 4)`, `carp(10, 4)` gibi üç
ayrı satır yazmaya gerek kalmıyor. `sonuclar` dizisi hem `tablo` hem de kendisi `main`
içinde yerel değişken olarak tanımlı; ikisi de sınıfı **çağıran** — `main` onları ayırıyor,
doldurup okuyor, işlev çağrısı bitince kapsam dışına çıkıp kendiliğinden geri veriyor. Bu,
`geri_baglam.c`'deki `Baglam b`'nin sahiplik biçimiyle aynı: dizi de yapı da `dil`
sınıfının otomatik ömrüyle yönetiliyor, ama dizinin *içeriği* (hangi işlevlerin hangi sırada
durduğu) tamamen `main`'in kararı — `topla`, `cikar` ve `carp` bu kararda hiç söz sahibi
değil, yalnız çağrıldıklarında kendi işlerini yapıyorlar.

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

Bir fonksiyon işaretçisinin imzası (`int (*)(int, int)` gibi) yalnız parametre ve dönüş
tiplerini eşleştirir — gösterdiği işlevin **ne yaptığını** ya da **yan etkilerinin**
neler olduğunu söylemez. Yukarıdaki `topla` ve `carp` aynı imzayı paylaşıyor ama tamamen
farklı işler yapıyor; bir geri çağrı parametresi alan bir işlev, kendisine geçirilen
işlevin davranışı hakkında derleme zamanında hiçbir garanti alamaz. Bu, işaretçi
tiplerinin sahiplik bilgisini taşımadığı önceki derslerdeki bulguyla aynı sınırın bir
başka biçimidir: tip, **şekli** garanti eder, **anlamı** garanti etmez.

Bu sınır, geri çağrıya taşınan bağlamla birleştiğinde ayrı bir riske dönüşür. `geri_baglam.c`'de
`dizi_uzerinde_calistir` çağrısı `birikti`'ye geçtiği anda `b` hâlâ `main`'in kapsamında,
dolayısıyla `baglam`'ın gösterdiği bellek geçerli. Ama bir geri çağrı **daha sonra**,
örneğin bir olay kuyruğuna kaydedilip çağıran işlev geri döndükten sonra çalışacaksa,
bağlam olarak geçirilen adres artık `dil` sınıfının otomatik ömrünü aşmış olabilir —
04. dersteki sarkan işaretçi durumunun aynısı, burada geri çağrı biçiminde. Bu blok bilerek
koşturulmuyor: bir işlev, yerel bir `Baglam` değişkeninin adresini bir geri çağrı kaydına
yazıp döner, sonra kayıtlı geri çağrı tetiklendiğinde `baglam`, artık kapsamı kapanmış bir
çağrı çerçevesini gösteriyor olurdu — dolaylamak tanımsız davranıştır. `dizi_uzerinde_calistir`
örneğinde bu risk yok, çünkü geri çağrı `main`'in kendi çağrı çerçevesi içindeyken, `b`
hâlâ yaşarken çalışıyor; risk yalnız bağlamın ömrü ile geri çağrının çalışma anı
**ayrıştığında** ortaya çıkıyor. Bu yüzden bağlam sağlayan tarafın (çağıran) yükümlülüğü
yalnız belleği bırakmakla sınırlı değil — geri çağrı ne zaman tetiklenebilirse bağlamın o
âna kadar yaşamasını da garanti etmek zorunda.

Bu desenin en yaygın kullanımı C standart kütüphanesinde: `qsort`, bir diziyi sıralamak
için bir `int (*)(const void *, const void *)` karşılaştırma işlevi ister, `bsearch` aynı
imzayı arama için kullanır. `qsort`'un kendisi hangi tipi sıraladığını **bilmiyor** —
`islem_tablosu.c`'deki `tablo[i]` çağrısıyla aynı dolaylama düzeni, ama bu kez dolaylanan
işlev çağıranın verdiği bir karşılaştırma kararı. `qsort`'a geçirilen karşılaştırma işlevi
de kendi parametrelerini önce doğru tipe dönüştürmek zorunda — tıpkı `birikti`'nin
`void *baglam`'ı önce `Baglam *`'a çevirmesi gibi; imzadaki `const void *` burada da tip
bilgisini taşımıyor, yalnız saklıyor. `dagitim tablosu` ve `qsort` deseni arasındaki fark
yalnız ölçek: biri üç sabit işlev arasında seçim yapıyor, diğeri çağıranın **her seferinde
farklı bir karşılaştırma mantığı** sağlamasına izin veriyor — ikisinin de temeli aynı: bir
işlevi veri gibi taşıyıp gerektiğinde dolaylı çağırmak.

## Sahiplik Ekseni A Payı Boyunca: Dört Sınıf, Bir Sonuç

İşaretçiler konusunun altı dersi aynı soruyu altı farklı işaretçi biçiminde sordu: **bu belleği kim
sahipleniyor?** 01 ve 02. derslerde yanıt `dil`'di — `main` içindeki yerel değişkenler ve
diziler, kapsam kapanınca kendiliğinden geri veriliyordu, hiçbir taraf `free` çağırmıyordu.
03. derste sahiplik yine `dil`'de kaldı, ama sorun sahiplikten çok **bilgi kaybı**ydı: bir
dizi işaretçiye bozunduğunda uzunluğunu kaybediyordu, bu da `en_buyuk`, `matris_bozunmasi.c`
ve `ciktisi_da_donen.c`'nin üç farklı kanaldan aynı eksik bilgiyi tamamlamasını gerektiriyordu.
04. derste sahiplik üç kaynağa (dil, çağıran, çağrılan) ayrıldı ve ortak sonuç sarkan
işaretçiydi — bırakılmış bir bloğu göstermeye devam eden bir değer. 05. derste sahiplik ilk
kez **gizlenen** bir sınıfa taşındı: opak tip, `çağrılan`'ın kendi verisini tuttuğu ve
çağırana yalnız ödünç verdiği bir sözleşmeydi. Bu son derste sahiplik yeniden `çağıran`'a
döndü — ama bu kez veri değil, bir **işlev seçimi** ve ona eşlik eden bağlam söz konusu;
`geri_baglam.c`'deki `Baglam b` ve `islem_tablosu.c`'deki `tablo`/`sonuclar` dizileri,
`main`'in ayırdığı ve `main`'in geri aldığı belleklerdi.

Dört sınıfın (dil, çağıran, çağrılan, sahipsiz) ortak noktası, hiçbirinin işaretçinin
**tipinde** görünmemesiydi — `int *`, `Kayit *` ya da `int (*)(int, int)` hiçbiri hangi
tarafın bırakmakla yükümlü olduğunu söylemiyordu. Bu bilgi her zaman ya bir isimlendirme
kuralıyla (`_olustur`/`_yok_et` çiftleri), ya bir yorumla, ya da bir sözleşme belgesiyle
**dışarıdan** taşınmak zorundaydı. Sıradaki konu, Dinamik Bellek, bu dört sınıfı artık
`malloc`/`free` çiftinin kendisine uygulayacak; buradaki sınıflandırma değişmeyecek, yalnız
hangi işlevlerin hangi sınıfı temsil ettiği netleşecek.

## Özet

- Bir fonksiyon işaretçisi (`int (*)(int, int)`) derlenmiş bir işlevin adresini tutar ve
  o işlevi dolaylı biçimde çağırmayı sağlar; farklı işlevlere yeniden atanabilir.
- Bir işlevi başka bir işleve parametre olarak geçirmek bir **geri çağrı** kurar;
  çağrılan işlevin davranışı, çalışma zamanında hangi işlevin geçirildiğine bağlı olarak
  değişir.
- Bir geri çağrıya taşınan bağlam, çoğunlukla `dil` sınıfında bir yerel değişkenin
  adresidir; geri çağrı işlevi bu bağlamı **sahiplenmez**, yalnız kullanır.
- Bağlamı sağlayan taraf **çağıran**dır ve bağlam dinamik olarak ayrılmış olsaydı da onu
  bırakma yükümlülüğü yine çağıranda kalırdı.
- Bir fonksiyon işaretçisinin imzası yalnız parametre ve dönüş tiplerini eşleştirir;
  gösterdiği işlevin davranışını garanti etmez.
- Bir fonksiyon işaretçisi dizisi (`islem_tablosu.c`'deki `tablo`) çalışma zamanında
  indeksle seçilen bir dağıtım tablosu kurar; `qsort`/`bsearch`'ün karşılaştırma
  parametresi aynı deseni standart kütüphanede kullanır.

## Sonraki Adım

İşaretçiler konusu burada tamamlanıyor: dört sınıf (dil, çağıran, çağrılan, sahipsiz) altı ders
boyunca hem veri işaretçileri hem işlev işaretçileri üzerinden ölçüldü. Her derste
sınıfı belirleyen şey işaretçinin tipi değil, hangi tarafın ayırma ve bırakma işlemini
üstlendiğiydi — bu ayrım kaynak kodda hiçbir zaman doğrudan görünmedi, yalnız isimlendirme
ve sözleşmeyle iletildi. Sıradaki konu, Dinamik Bellek, bu dört sınıfı `malloc`, `calloc`,
`realloc` ve `free` işlevlerinin kendi davranışlarına — başarısız ayırma, yeniden
boyutlandırmanın taşıdığı riskler ve serbest bırakma disiplini — taşıyor.
