---
title: 'İşaretçi Aritmetiği'
source: 'https://academia.sh/tr/kurslar/c-bellek/isaretci-aritmetigi'
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 işaretçiyi artırma ve azaltmanın tip boyutuna göre nasıl ilerlediğini ölçer'
    - 'İki işaretçinin farkını eleman biriminde hesaplar ve sırasını karşılaştırma ile ölçer'
    - 'Dizi sınırlarının dışına taşan bir aritmetiğin neden koşturulmadığını açıklar'
---

# İşaretçi Aritmetiği

Bir işaretçiyi artırmanın, azaltmanın ve iki işaretçiyi çıkarmanın neden bayt cinsinden değil eleman cinsinden işlediği; sınır dışına taşan bir aritmetiğin neden hiç koşulmadığı.

Önceki ders `yerel dizi` satırında `son - ilk` farkını **3** olarak verdi: dört elemanlı
bir dizide ilk ve son eleman arasında üç adım var. Ama bu fark bayt cinsinden değil —
`son` ve `ilk` arasındaki gerçek bellek uzaklığı, dizinin eleman tipi kaç bayt tutuyorsa
onun katıdır. Bu ders o katsayının nereden geldiğini ve bir işaretçiyi artırıp
azaltmanın ne anlama geldiğini ölçüyor. Sınıf hâlâ **dil**: bir dizinin elemanları
arasında gezinmek, herhangi bir ayırma ya da bırakma çağrısı gerektirmiyor.

## Artırma ve Azaltma: Tip Boyutuna Göre İlerleme

```c
// artirma.c — bir isaretciyi artirmak, tip boyutu kadar ilerler
#include <stdio.h>

int main(void) {
    int dizi[5] = {10, 20, 30, 40, 50};
    int *p = dizi;

    printf("%s %d\n", "ilk eleman", *p);
    p++;
    printf("%s %d\n", "bir artirmadan sonraki eleman", *p);
    p += 2;
    printf("%s %d\n", "uc artirmadan sonraki eleman", *p);
    p--;
    printf("%s %d\n", "bir azaltmadan sonraki eleman", *p);

    double cift[3] = {1.5, 2.5, 3.5};
    double *d = cift;
    d++;
    printf("%s %.1f\n", "double dizide bir artirmadan sonraki eleman", *d);
    return 0;
}
```

```
ilk eleman 10
bir artirmadan sonraki eleman 20
uc artirmadan sonraki eleman 40
bir azaltmadan sonraki eleman 30
double dizide bir artirmadan sonraki eleman 2.5
```

**IS5.** `p++` işaretçiyi bir sonraki `int`'e taşıyor — yazılan adım her zaman **bir
eleman**, bir bayt değil. `p += 2` iki elemanı birden atlıyor: indeks 1'den indeks 3'e.
`p--` bir eleman geri dönüyor. `double cift` dizisinde de aynı `p++` yazımı kullanıldı
ve yine tam olarak **bir eleman** ilerledi — `int` ve `double` farklı boyutlarda olsa
bile, artırma işleci her ikisinde de "bir sonraki eleman" anlamına geliyor. Boyutun
kendisi (`sizeof`) hiçbir yerde basılmadı; ölçüm yalnız işaret edilen **değer** üzerinden
yapıldı.

## İki İşaretçinin Farkı ve Sırası

```c
// fark_ve_sira.c — iki isaretcinin farki eleman biriminde, sirasi karsilastirma ile olculur
#include <stdio.h>

int main(void) {
    int dizi[6] = {1, 2, 3, 4, 5, 6};
    int *ilk = &dizi[0];
    int *orta = &dizi[3];
    int *son = &dizi[5];

    printf("%s %d\n", "orta eksi ilk", (int)(orta - ilk));
    printf("%s %d\n", "son eksi orta", (int)(son - orta));
    printf("%s %d\n", "ilk once orta mi geliyor", (int)(ilk < orta));
    printf("%s %d\n", "son once orta mi geliyor", (int)(son < orta));

    int *gezici = ilk;
    int adim = 0;
    while (gezici < son) {
        gezici++;
        adim++;
    }
    printf("%s %d\n", "ilkten sona kadar atilan adim", adim);
    return 0;
}
```

```
orta eksi ilk 3
son eksi orta 2
ilk once orta mi geliyor 1
son once orta mi geliyor 0
ilkten sona kadar atilan adim 5
```

**IS6.** `orta - ilk` **3** veriyor: `dizi[3]` ile `dizi[0]` arasında üç eleman aralığı
var. Bu çıkarma işleminin sonucu her zaman **eleman sayısı**dır, iki adresin bayt
farkı değil — `orta - ilk` ifadesi `3` yazdırıyor, adreslerin kendisi hiç görünmüyor.
`ilk < orta` karşılaştırması **1** veriyor çünkü `ilk`, dizide `orta`'dan önce duruyor;
bu sıralama, `<` işlecinin aynı dizi içindeki iki işaretçi için tanımlı olmasından geliyor.
`gezici < son` koşuluyla kurulan döngü, `ilk`'ten `son`'a kadar tam **5** adımda
ilerliyor — bu da `son - ilk` farkıyla birebir eşleşiyor.

## `char` Dizisinde Aritmetik: Eleman Adımı Bayt Adımına Eşitken

Önceki iki örnekte `int` ve `double` dizileri kullanıldı; her ikisinde de bir artırma
birden fazla bayt ilerledi, ama bu ilerleme hiç ölçülmedi — yalnız işaret edilen değer
okundu. `char` tipi burada özel bir durum oluşturuyor: `sizeof(char)` her zaman **1**'dir,
bu yüzden bir `char *`'ı artırmak, eleman cinsinden bir adım ile bayt cinsinden bir adımı
aynı anda karşılıyor. Bu, bir dizginin karakterleri arasında gezinirken sıkça karşılaşılan
bir durum.

```c
// karakter_gezinme.c — char isaretcisinde bir artirma, bir sonraki karaktere tasir
#include <stdio.h>

int main(void) {
    char dizgi[] = "abcd";
    char *harf = dizgi;

    printf("%s %c\n", "ilk karakter", *harf);
    harf++;
    printf("%s %c\n", "bir artirmadan sonraki karakter", *harf);

    char *son_harf = &dizgi[3];
    printf("%s %d\n", "son harf eksi ilk harf", (int)(son_harf - dizgi));
    printf("%s %c\n", "son karakterin degeri", *son_harf);
    return 0;
}
```

```
ilk karakter a
bir artirmadan sonraki karakter b
son harf eksi ilk harf 3
son karakterin degeri d
```

**IS7.** `harf++` çağrısı `dizgi[0]`'dan `dizgi[1]`'e geçiyor; okunan değer `'a'`'dan
`'b'`'ye değişiyor. `son_harf - dizgi` farkı **3** veriyor — `dizgi[3]`, dizinin
başlangıcından üç eleman uzakta. Bu fark, `int` dizisindeki `orta - ilk` örneğiyle aynı
kuralı izliyor: sonuç eleman cinsinden. Farkın `char` için aynı zamanda bayt cinsinden bir
uzaklığa da eşit olması, `sizeof(char)`'ın **1** olmasının doğrudan sonucu — bu, kuralın
bir istisnası değil, kuralın kendisinin `char` özelinde aldığı biçim.

## İki Boyutlu Dizide Satır Adımı

Buraya kadarki üç örnek tek boyutlu diziler kullandı; artırma her zaman **bir eleman**
ilerledi. İki boyutlu bir dizide bu kural değişmiyor, ama "eleman" tanımı değişiyor:
`int matris[3][4]` üzerinde bir satır işaretçisi (`int (*)[4]`) tanımlandığında, bir
artırma tek bir `int` değil, **tüm bir satırı** (4 `int`) atlıyor.

```c
// satir_adimi.c — iki boyutlu bir dizide satir isaretcisi tum satiri bir adim sayar
#include <stdio.h>

int main(void) {
    int matris[3][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };
    int (*satir)[4] = matris;

    printf("%s %d\n", "ilk satirin ilk elemani", satir[0][0]);
    satir++;
    printf("%s %d\n", "bir artirmadan sonraki satirin ilk elemani", satir[0][0]);
    printf("%s %d\n", "ayni satirin ucuncu elemani", satir[0][2]);

    int *duz = &matris[0][0];
    int *ikinci_satir_basi = &matris[1][0];
    printf("%s %d\n", "ikinci satir basi eksi ilk eleman", (int)(ikinci_satir_basi - duz));
    return 0;
}
```

```
ilk satirin ilk elemani 1
bir artirmadan sonraki satirin ilk elemani 5
ayni satirin ucuncu elemani 7
ikinci satir basi eksi ilk eleman 4
```

**IS8.** `satir` değişkeni `int (*)[4]` tipinde — "dört `int`'lik bir dizinin
adresi" tipini taşıyor, düz bir `int *` değil. `satir++` çağrıldığında işaretçi bir
sonraki **satıra** geçiyor: `satir[0][0]` değeri `1`'den `5`'e atlıyor, yani `matris`'in
ikinci satırının başına. Bu, `artirma.c`'deki `p++`'ın bir `int` kadar ilerlemesiyle aynı
kuralın farklı bir tip üzerindeki görünümü: adım her zaman **işaret edilen tipin
boyutu** kadardır, burada o tip tek bir `int` değil dört `int`'lik bir satır. Düz bir
`int *` (`duz`) ile ikinci satırın başı arasındaki fark **4** çıkıyor — bu da `int
matris[3][4]`'ün bir işleve geçtiğinde neden `int **` değil `int (*)[4]`'e bozunduğunu
doğruluyor: satır boyutu (4) tipin bir parçası olarak taşınıyor, aksi hâlde `satir++`
doğru elemana ilerleyemezdi.

## Dinamik Ayrılmış Bir Dizide Aynı Kural

Buraya kadarki üç örnek yerel (`dil` sınıfı) dizilerde çalıştı. Aynı aritmetik kuralları,
dinamik olarak ayrılmış bir blok üzerinde de **birebir** geçerli — tip boyutuna göre
ilerleme, işaretçinin dizinin nereden geldiğini bilmesini gerektirmiyor.

```c
// dinamik_artirma.c — dinamik ayrilan bir blokta isaretci aritmetigi ayni kurali izler
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int uzunluk = 4;
    int *dizi = malloc((size_t)uzunluk * sizeof *dizi);
    if (dizi == NULL) {
        printf("%s\n", "ayirma basarisiz");
        return 1;
    }
    for (int i = 0; i < uzunluk; i++) {
        dizi[i] = (i + 1) * 10;
    }

    int *p = dizi;
    printf("%s %d\n", "ilk eleman", *p);
    p += 2;
    printf("%s %d\n", "iki artirmadan sonraki eleman", *p);

    int *son = dizi + uzunluk - 1;
    printf("%s %d\n", "son eksi ilk", (int)(son - dizi));

    free(dizi);
    dizi = NULL;
    return 0;
}
```

```
ilk eleman 10
iki artirmadan sonraki eleman 30
son eksi ilk 3
```

**IS9.** `dizi`, `malloc` ile ayrılmış bir blok, ama `p += 2` çağrısı `artirma.c`
örneğindeki yerel diziyle **tamamen aynı sonucu** üretiyor: iki eleman atlıyor, `10`'dan
`30`'a. `son - dizi` farkı **3** — `fark_ve_sira.c`'deki `orta - ilk` örneğiyle aynı
kural. Aradaki fark aritmetikte değil, sahiplikte: bu blok `malloc` ile isteniyor, bu
yüzden sınıf burada **çağıran** — `dizi`'yi ayıran `main`, onu kullanan da `main`, ve
bırakma yükümlülüğü de `main`'de kalıyor. Aritmetiğin kendisi bu sahiplik bilgisinden
tamamen bağımsız: bir işaretçinin gösterdiği blok `dil` sınıfında yerel bir dizi olsun ya
da `çağıran` sınıfında dinamik bir blok olsun, `p++`'ın ne kadar ilerleyeceği yalnız
**tipe** bakar, belleğin nereden geldiğine bakmaz.

## İndeksleme ile Aritmetiğin Denkliği

Buraya kadar `p[i]` yazımı ve `p + i` yazımı ayrı ayrı kullanıldı, ama aralarındaki
ilişki hiç ölçülmedi. C'de köşeli parantez indekslemesi, aritmetik üzerine tanımlı bir
**kısaltmadır**: `p[i]` ifadesi tam olarak `*(p + i)` anlamına geliyor, ne bir bayt
farklı ne de ayrı bir kural.

```c
// indeks_denkligi.c — p[i] yazimi ile *(p + i) yazimi ayni adresi gosterir
#include <stdio.h>

static int indeksle(int *p, int i) {
    return p[i];
}

static int dolaylayarak_topla(int *p, int i) {
    return *(p + i);
}

int main(void) {
    int dizi[5] = {100, 200, 300, 400, 500};
    int *p = dizi;

    int indeks_sonucu = indeksle(p, 3);
    int aritmetik_sonucu = dolaylayarak_topla(p, 3);
    printf("%s %d\n", "p uc indeksi", indeks_sonucu);
    printf("%s %d\n", "p artirilmis uc kere dolaylanmis", aritmetik_sonucu);
    printf("%s %d\n", "iki yazim ayni deger mi veriyor", (int)(indeks_sonucu == aritmetik_sonucu));

    int sifirinci_indeks = indeksle(p, 0);
    int sifirinci_aritmetik = dolaylayarak_topla(p, 0);
    printf("%s %d\n", "sifirinci indeks", sifirinci_indeks);
    printf("%s %d\n", "dolaylanmis p ile ayni mi", (int)(sifirinci_indeks == sifirinci_aritmetik));
    return 0;
}
```

```
p uc indeksi 400
p artirilmis uc kere dolaylanmis 400
iki yazim ayni deger mi veriyor 1
sifirinci indeks 100
dolaylanmis p ile ayni mi 1
```

**IS10.** `p[3]` ve `*(p + 3)` ifadeleri aynı değeri (**400**) veriyor ve eşitlik
karşılaştırması **1** çıkıyor — bu, iki yazımın derleyici için tamamen aynı işlemi ifade
ettiğini doğruluyor. `p[0]` ile `*p` arasındaki eşitlik de aynı kuralın bir uç durumu:
sıfırıncı indeks, hiç ilerlemeden doğrudan dolaylamakla özdeş. Bu denklik, bu dersteki
`p++`, `p += n` ve `son - ilk` ölçümlerinin hepsinin neden `dizi[i]` yazımıyla tutarlı
sonuç verdiğini açıklıyor: indeksleme, aritmetiğin üstüne kurulmuş bir gösterim
kolaylığı, ayrı bir mekanizma değil. Sınıf yine **dil**: hem `p[3]` hem `*(p + 3)` aynı
yerel diziyi gösteriyor, aradaki fark yalnız yazım tercihidir. Bu denklik aynı zamanda
şunu da açıklıyor: `dizi[i]` yazımı bile aslında bir işaretçi aritmetiğidir, çünkü `dizi`
bir işleve ya da bir işleç bağlamına girdiğinde ilk elemanının adresine bozunur ve
`dizi[i]`, tam olarak `*(dizi + i)`'ye eşdeğer hâle gelir.

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

Bir işaretçi aritmetiği, yalnız **aynı dizinin içinde ya da onun bir eleman ötesinde**
tanımlıdır — dizinin son elemanının bir ötesini göstermek (döngü sonlandırma için)
geçerlidir, ama o adresi **dolaylamak** ya da diziyi iki eleman aşarak ilerletmek
tanımsız davranıştır. Yukarıdaki `fark_ve_sira.c` örneğinde `gezici`, döngü sırasında
en fazla `son` adresine kadar ilerliyor ve orada duruyor — dizinin sınırını aşan bir
artırma hiç koşturulmuyor. Bu sınır, derleyicinin denetlediği bir şey değil: `p += 100`
yazmak da derlenir, ama sonucu tanımsızdır ve bu ders böyle bir çağrıyı hiç içermiyor.
Aritmetiğin geçerliliği, kodu yazanın dizinin sınırlarını bilmesine bağlıdır — tip
sistemi bu sınırı taşımaz.

Aynı sınır, `dizi` gibi statik ayrılmış bir bloğun **dışına** çıkan işaretçi değerleri
için de geçerli: `p - 1` ile dizinin başlangıcının bir öncesini göstermek, ya da
`son + 1`'den daha ileri gitmek, aynı tanımsız davranış sınıfına giriyor. Sınırın
"bir eleman ötesi" olarak tanımlanmasının nedeni tam olarak bu: döngü sonlandırma
koşulları (`gezici < son` gibi) o tek adrese güvenle ulaşabilsin diye standart bu
kadarını garanti ediyor, daha fazlasını değil.

Bu sınır, iki **ilgisiz** diziye ait işaretçiler arasında da geçerli: `int a[4]` ile
`int b[4]` ayrı ayrı tanımlanmış iki yerel diziyse, `&b[0] - &a[0]` derlenir ama sonucu
**tanımsızdır** — çıkarma işleci yalnız aynı dizi (ya da aynı dizinin bir eleman ötesi)
içindeki iki işaretçi için anlamlı bir eleman sayısı üretmeyi garanti ediyor; iki farklı
dizinin bellekte birbirine göre nerede durduğu derleyicinin kararına bırakılmış bir
ayrıntı ve program bu ayrıntıya güvenemez. Yukarıdaki `fark_ve_sira.c` ve
`dinamik_artirma.c` örneklerinin ikisi de bu kuralı dolaylı olarak gözetiyor: her ikisi
de farkı yalnız **tek bir dizinin ya da tek bir bloğun içindeki** iki işaretçi arasında
alıyor, `a` ile `b` gibi ilgisiz iki bloğu hiç karşılaştırmıyor.

## Özet

- İşaretçi artırma ve azaltma (`p++`, `p--`, `p += n`) her zaman **tip boyutu kadar**
  ilerler; adım sayısı eleman cinsindendir, bayt cinsinden değildir.
- İki işaretçinin farkı (`son - ilk`) aralarındaki **eleman sayısını** verir; adresin
  kendisi hiç basılmaz.
- İki işaretçi `<`, `>` gibi karşılaştırma işleçleriyle **sıralanabilir** — aynı dizi
  içindeyse bu sıralama dizideki konum sırasıyla eşleşir.
- Dizi sınırının bir ötesini **göstermek** geçerlidir, ama o adresi **dolaylamak**
  tanımsız davranıştır; iki ilgisiz dizinin işaretçileri arasında fark almak da aynı
  sınıftandır — bu derste hiçbiri koşturulmadı.
- `p[i]` yazımı `*(p + i)`'nin bir kısaltmasıdır; iki yazım her zaman aynı adresi
  gösterir ve aynı değeri döndürür.
- Aritmetik kuralları, işaretçinin gösterdiği blok yerel bir dizi (`dil`) olsun ya da
  dinamik olarak ayrılmış bir blok (`çağıran`) olsun **değişmez**; adım yalnız tipe
  bakar, belleğin kaynağına bakmaz. Bu yüzden dersin ölçtüğü bellek **dil** sınıfındadır:
  kuralı belleğin sahibi değil, dilin kendisi koyar.

## Sonraki Adım

Bir dizi adı, çoğu bağlamda kendiliğinden ilk elemanının adresine **bozunuyor** —
`int *ilk = dizi` yazımı bunu zaten kullandı ama üzerinde durmadı. Bir sonraki ders,
Diziler ve İşaretçiler, bu bozunmanın ne zaman gerçekleştiğini, bir dizinin bir
işleve nasıl (ve neden uzunluk bilgisi olmadan) geçtiğini ve `çağıran` sınıfına ilk
adımı — dinamik olarak ayrılmış bir dizinin sahipliğini — ölçüyor.
