---
title: 'Ön İşlemci'
source: 'https://academia.sh/tr/kurslar/c-temelleri/on-islemci'
course: 'C Temelleri'
language: tr
updated: '2026-10-21T05:02:45+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Ön işlemcinin dört farklı yönergesinin ortak işleyişini (metin ikamesi) tanımlar'
    - 'Bir makronun tip bilmediğini, aynı tanımın iki farklı tiple çalıştığını göstererek kanıtlar'
    - 'Makro argümanının birden çok kez yerine konabileceğini ve bunun riskini bir örnekle gösterir'
---

# Ön İşlemci

Ön işlemcinin dört farklı yönergesi — makro değeri, koşullu gövde, ad birleştirme, içerme — aslında tek bir işin dört görünümüdür: metin ikamesi. Makro tip bilmediği için aynı ifadeyi iki farklı tiple doğru sonuç verecek şekilde üretir; ama argümanını birden çok kez yerine koyabildiği için yan etkili bir çağrıyı gerçek bir işlevden farklı sayıda çalıştırabilir.

Önceki ders `baslik metni` satırının arkasındaki mekanizmayı açtı ve ön işlemcinin bir
dosyayı kopyaladığını, ama kaynağın geri kalanının bu kopyalamadan habersiz kaldığını
gösterdi. Bu ders aynı gözlemi ön işlemcinin öteki üç kararına — makro değeri, koşullu
gövde, ad birleştirme — taşıyor ve dördünün de aslında tek bir işin görünümü olduğunu ortaya
koyuyor: kaynaktaki bir metni, derleyici hiç haberdar olmadan, başka bir metinle değiştirmek.

Bu dört karar kursun ilk dersinden beri tek tek görüldü: `KAT` metni, `#ifdef AYRINTILI`
seçimi, `BIRLESTIR(topla, _uc)` adı, `asama.h`nin kopyalanması. O derste dördü de yan yana,
tek bir koşumun parçası olarak geçti. Bu ders her birine ayrı ayrı döner ve ortak paydayı
gösterir: dördü de derleyici hiç devreye girmeden, salt metin üzerinde çalışır.

## Dördü de Metin İkamesi

Ön işlemcinin bütün yönergeleri tek bir işi yapar: kaynaktaki bir metni başka bir metinle
değiştirmek.

| Yönerge | Neyi neyle değiştirir |
|---|---|
| `#define AD deger` | `AD` geçtiği her yeri `deger` ile |
| `#define AD(x) govde` | `AD(gercek)` çağrısını `govde` içindeki `x`ler `gercek` ile değiştirilmiş haliyle |
| `a##b` | İki token'ı tek bir yeni token ile |
| `#ifdef` / `#if` | Bir kod bloğunu ya kendisiyle ya da boş metinle |
| `#include "d.h"` | Bir satırı, dosyanın tüm içeriğiyle |

Beşi de aynı sınıfa girer, çünkü beşi de derleyici devreye girmeden, kaynağın metnini
değiştirir. Aralarındaki fark yalnız neyi neyle değiştirdikleridir — bir ad mı, bir çağrı mı,
iki token mı, bir kod bloğu mu, yoksa bir dosyanın tamamı mı.

Bu ortak paydanın bir sonucu var: ön işlemcinin ürettiği metin, derleyici için kaynaktan
elle yazılmış metinden ayırt edilemez. Derleyici bir satırın bir makrodan mı geldiğini, bir
başlıktan mı kopyalandığını, yoksa doğrudan mı yazıldığını bilmez ve bilmesine gerek de
yoktur — önündeki metin ne ise, onu derler. Bu görünmezlik hem ön işlemcinin gücünün hem
sınırlarının kaynağı: bir yandan kaynağı programcının elle yazmayacağı kadar tekrarlı
biçimlerde üretebiliyor, öte yandan ürettiği metnin doğruluğunu kendisi denetlemiyor.

Tablodaki beşinci satır — `#include` — önceki dersin bütün konusuydu, ama orada bir
paylaşım aracı olarak görüldü. Burada aynı yönergeye ikame ailesinin bir üyesi olarak
bakılıyor: `#include "asama.h"` satırı da tıpkı bir makro çağrısı gibi, kendi yerine başka
bir metni koyar. Aradaki tek fark ölçek — bir makro birkaç token'ı değiştirirken, bir
`#include` bütün bir dosyayı değiştirir. Ön işlemcinin gözünden ikisi arasında bir fark
yoktur; ikisi de aynı mekanizmanın, metni okuyup başka bir metinle değiştirmenin, farklı
boyutlardaki uygulamalarıdır.

## Makro Tip Bilmez

Bir makro parametre alsa bile bir işlev değildir; parametre burada bir tip bildirmez, yalnız
gövde içinde nereye yapıştırılacağını gösteren bir yer tutucudur. Bu yüzden aynı makro,
farklı tiplerdeki argümanlarla doğru sonuç üretebilir. Gerçek bir C işlevi böyle davranamaz:
bir işlevin her parametresi tek bir tiple bildirilir ve çağrıda verilen argüman o tipe
dönüştürülür — dönüşüm bazen sessiz, bazen bilgi kaybıyla olur. Makronun parametresi bu
dönüşümden tamamen bağışıktır, çünkü dönüştürülecek bir tip zaten yoktur.

```c
// hesap.c — ayni makro iki farkli tiple cagriliyor
#include <stdio.h>

#define KARE(x) ((x) * (x))

int main(void) {
    int tam = KARE(4);
    double kesirli = KARE(2.5);

    printf("tam: %d\n", tam);
    printf("kesirli: %.2f\n", kesirli);

    return 0;
}
```

```
tam: 16
kesirli: 6.25
```

**DD9.** `KARE(4)` ön işlemcide `((4) * (4))` metnine, `KARE(2.5)` ise `((2.5) * (2.5))`
metnine dönüşüyor — ikisi de aynı gövdeden, `x`in yerine konan tek token değişerek üretildi.
Ön işlemci bu değişimi yaparken `4`ün bir tam sayı, `2.5`in bir kayan noktalı sayı olduğunu
hiç bilmiyor; onun gördüğü yalnız iki ayrı metin parçası. Sonucun `int` mi `double` mı
olacağına derleyici karar veriyor — ama bu karar, makronun genişlemesinden **sonra**, tıpkı
kaynağa elle `((4) * (4))` ve `((2.5) * (2.5))` yazılmış gibi veriliyor.

Bu, `KARE`in neden bir tip parametresi almadığını da açıklıyor: alamaz, çünkü zaten tip diye
bir kavramı yok. Aynı gövde her tipe uyuyor, çünkü hiçbir tipe özel yazılmadı — yalnız
metne yapıştırıldı.

Bir gerçek işlev aynı işi yapamaz — en azından tek bir tanımla. `int kare(int x)` yazılırsa
yalnız `int` argümanlarla çalışır; `KARE(2.5)`e benzer bir çağrı, `2.5`i sessizce `int`e
kesip `2`nin karesini, `4`ü verirdi. `KARE` makrosunun `int` ve `double` ile aynı gövdeyle
doğru sonuç vermesi bu yüzden bir kolaylık değil, tipin ön işlemci aşamasında hiç var
olmamasının doğrudan sonucu. Aynı esneklik bir bedel taşır: makro, argümanına hangi tipin
geleceğini denetleyemez, çünkü denetleyecek bir tip sistemi yoktur — `KARE("metin")` yazılsa
bile ön işlemci bunu reddetmez, yalnız `(("metin") * ("metin"))` metnini üretir ve
sorunu derleyiciye devreder.

## Koşullu Gövde: Bir Adım İleri

Önceki dersler koşullu gövdeyi yalnız bir adın **tanımlı olup olmadığına** (`#ifdef`)
bakarak seçti. `#if` bundan bir adım ilerisini yapar: bir tam sayı sabit ifadesinin
**değerini** ön işlemcide hesaplar. Bu, ön işlemcinin tamamen boş bir kavrayışa sahip
olmadığını gösteriyor — metni okurken bazı basit hesapları da yapabiliyor, ama bu hesap
derleyicinin zengin tip sisteminden çok daha dar bir aritmetiktir.

```c
// esik.c — #if bir tam sayi sabit ifadesini on islemcide degerlendirir
#include <stdio.h>

#define ESIK 5

#if ESIK > 3
static const char *sonuc = "esik 3'ten buyuk";
#else
static const char *sonuc = "esik 3'ten kucuk ya da esit";
#endif

int main(void) {
    printf("%s\n", sonuc);
    return 0;
}
```

```
esik 3'ten buyuk
```

**DD10.** `#if ESIK > 3` derleyicinin `>` işlecini kullandığı yerle aynı sözdizimini taşısa
da, aynı hesap makinesini kullanmaz. Ön işlemcinin kendi aritmetiği vardır: bütün tam sayılar
tek bir geniş tipe genişletilir, kayan noktalı sayı hiç yoktur ve tanımsız bir ad `0` olarak
okunur. `ESIK`in değeri önce `5` metnine açılır, sonra `5 > 3` ön işlemcinin kendi
aritmetiğiyle hesaplanır ve sonuç `dogru` ya da `yanlis`a indirgenir. Derleyici bu hesabı hiç
görmez; onun önüne yalnız seçilen dal — burada `static const char *sonuc = "esik 3'ten
buyuk";` satırı — gelir.

Bu ayrım `#ifdef` ile `#if` arasındaki farkı da netleştiriyor. `#ifdef AD`, `AD`in tanımlı
olup olmadığına bakar — değerine hiç bakmaz; `AD` `0` olarak tanımlansa bile `#ifdef AD`
doğrudur. `#if AD`, tam tersine, `AD`in **değerini** ön işlemcinin kendi aritmetiğiyle
hesaplar; `AD` `0`sa `#if AD` yanlıştır. Kaynakta bu iki yönergeyi karıştırmak, tanımlı ama
sıfır bir sabitle çalışırken sessizce yanlış dalı seçtirebilir — bu ders bu iki yönergeyi
birbirinin yerine kullanmaz, ama aradaki farkı bilerek seçer.

`#if`in kendi aritmetiği sayesinde yapabildiği bir şey daha var: birden çok koşulu `&&`,
`||` ve karşılaştırma işleçleriyle tek bir satırda birleştirmek, ya da `#elif` ile art arda
sıralanan alternatiflerden ilk doğru olanı seçmek. `#ifdef` bunu yapamaz — yalnız tek bir
adın tanımlı olup olmadığını sorabilir, tanımlı iki addan hangisinin daha büyük bir sayıya
karşılık geldiğini soramaz. Bu, iki yönergenin neden birbirinin yerini tutmadığını bir kez
daha gösteriyor: biri varlığı sorar, öbürü değeri hesaplar.

## Ad Birleştirme: Aynı Gövdeden Farklı Adlar

Önceki derste `##` tek bir örnekte görüldü: iki sabit token'ı (`topla` ve `_uc`) birleştirip
tek bir ad üretti. Asıl gücü orada görünmedi, çünkü o çağrı yalnız bir kezdi. `##`nin gerçek
gücü, aynı makro gövdesinin farklı çağrılarda farklı adlar üretebilmesidir — bir makro
gövdesi, kaynakta yalnız bir kez yazılıp defalarca çağrılarak, her seferinde yeni ve
birbirinden bağımsız bir ada dönüşebilir.

```c
// uret.c — ayni makro govdesi iki farkli cagriyla iki farkli isleve donusuyor
#include <stdio.h>

#define ISLEV_URET(ad) int uret_##ad(int x) { return x + 1; }

ISLEV_URET(a)
ISLEV_URET(b)

int main(void) {
    printf("uret_a(5) = %d\n", uret_a(5));
    printf("uret_b(5) = %d\n", uret_b(5));
    return 0;
}
```

```
uret_a(5) = 6
uret_b(5) = 6
```

**DD11.** `ISLEV_URET(a)` satırı ön işlemcide `int uret_a(int x) { return x + 1; }` metnine,
`ISLEV_URET(b)` satırı ise `int uret_b(int x) { return x + 1; }` metnine dönüşüyor. Derleyici
`ISLEV_URET` diye bir ad hiç görmez; onun gördüğü kaynakta iki ayrı işlev tanımı var, ikisi de
elle yazılmış gibi duruyor. Aynı makro gövdesi, argümanı değiştirilerek istenildiği kadar
farklı işlev üretebilir — ama her üretilen ad, gövdenin geri kalanıyla birlikte tek bir
metin parçası olarak derleyiciye gider; ikisi arasında hiçbir bağ, hiçbir "aynı makrodan
geldi" bilgisi taşınmaz.

Kursun ilk dersindeki `BIRLESTIR(topla, _uc)` de aynı işlemi yapıyordu, ama tek bir çağrıyla.
Buradaki fark, aynı üretici gövdenin **birden çok kez** çağrılabilmesi — `ISLEV_URET` iki
kez kullanıldı ve iki farklı işlev doğdu. Kaynağa üçüncü bir `ISLEV_URET(c)` satırı
eklenseydi, derleyici üçüncü bir işlev daha görürdü; ad birleştirmenin ürettiği metin
miktarı, makronun kaç kez çağrıldığına bağlıdır, kaynağın uzunluğuna değil.

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

Bu dersin dört bölümü de makronun tip bilmediğini, koşullu gövdenin kendi aritmetiğiyle
karar verdiğini, ad birleştirmenin yeni tanımlayıcılar üretebildiğini gösterdi — üçü de
ön işlemcinin **gücünü** anlatıyordu. Bu son bölüm aynı gücün bir bedelini gösteriyor.
Makro bir işlev değildir; argümanı **birden çok kez** yerine konabilir. `KARE(x)` gövdesinde
`x` iki kez geçiyor — bu, argüman yan etkili bir ifadeyse iki kez çalıştırılacağı anlamına
gelir. Aynı işi yapan gerçek bir işlevde argüman her zaman **bir kez** hesaplanır.

```c
// karsilastir.c — makro arguman iki kez, islev arguman bir kez calistiriyor
#include <stdio.h>

#define KARE(x) ((x) * (x))

static int sayac = 0;

static int artir(void) {
    sayac++;
    return sayac;
}

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

int main(void) {
    sayac = 0;
    int makro_sonuc = KARE(artir());
    int makro_sayac = sayac;

    sayac = 0;
    int islev_sonuc = kare(artir());
    int islev_sayac = sayac;

    printf("makro: sonuc %d, sayac %d kez arttirildi\n", makro_sonuc, makro_sayac);
    printf("islev: sonuc %d, sayac %d kez arttirildi\n", islev_sonuc, islev_sayac);

    return 0;
}
```

```
makro: sonuc 2, sayac 2 kez arttirildi
islev: sonuc 1, sayac 1 kez arttirildi
```

**DD12.** `KARE(artir())` ön işlemcide `((artir()) * (artir()))` metnine dönüşüyor — kaynakta
`artir()` çağrısı artık **iki kez** yazılı. Program çalışırken bu iki çağrı da gerçekten
yapılıyor: `sayac` sıfırdan başlayıp önce `1`e, sonra `2`ye çıkıyor, çarpım `1 * 2 = 2`
veriyor. Hangi çağrının önce çalıştığı derleyiciye bağlıdır ve bu ders onu hiç iddia etmiyor
— iddia edilen tek şey, iki çağrının da gerçekleştiği ve `sayac`ın iki kez arttığıdır. `kare`
çağrısında ise `artir()` yalnız bir kez yazılı ve yalnız bir kez çalışıyor: argüman önce
hesaplanır, sonucu `kare`ye bir kez geçirilir, `x` gövde içinde ne kadar geçerse geçsin
artık sabit bir değerdir.

Bu fark, `KARE` gibi bir makroyu yan etkili bir argümanla çağırmanın neden riskli olduğunu
gösteriyor: makro yazarı argümanın kaç kez kullanılacağını gövdeye bakarak bilir, ama
çağıran taraf bunu görmeden, sıradan bir işlevmiş gibi çağırabilir. `KARE`in adı ve
kullanım biçimi bir işlevinkiyle aynı, ama sözleşmesi farklıdır — argüman yan etkisiz bir
ifade olmalıdır.

`KARE(x)` gövdesindeki `(x)` çevresindeki parantezlerin bu riskle bir ilgisi yok; onlar
ayrı bir sorunu çözüyor. Parantezsiz bir gövde (`x * x`) `KARE(1 + 2)`yi `1 + 2 * 1 + 2`
metnine açardı; çarpma toplamadan önce işlendiği için bu ifade `1 + 2 + 2 = 5` verir,
oysa beklenen `3`ün karesi, yani `9`dur — çarpma önceliği, toplamanın parçalarını
kendine argüman sanır. Parantezli gövde bunu önler: `((1 + 2) * (1 + 2))` önce iki parantezi
kendi bütünlüğü içinde değerlendirir, sonra çarpar, sonuç doğru biçimde `9` çıkar. Ama bu
parantezler argümanın kaç kez **yazıldığını** değiştirmez — `((1 + 2) * (1 + 2))` hâlâ
`1 + 2`yi iki kez taşıyor. İki sorun da aynı kökten gelir — argüman metne yerine konurken
çevresiyle etkileşir — ama çözümleri ayrıdır: parantez öncelik sırasını korur, argümanı
yan etkisiz tutmaksa çağıranın sorumluluğudur.

## Özet

- Ön işlemcinin bütün yönergeleri tek bir işi yapar: kaynaktaki bir metni başka bir metinle
  değiştirmek; makro değeri, koşullu gövde, ad birleştirme ve içerme bunun dört görünümüdür.
- Bir makro tip bilmez: aynı gövde, farklı tiplerdeki argümanlarla, ön işlemci hiç
  değişmeden, doğru sonuç üretebilir.
- `#if` bir tam sayı sabit ifadesini ön işlemcinin kendi aritmetiğiyle hesaplar; bu aritmetik
  derleyicinin tip sistemiyle aynı değildir.
- Aynı makro gövdesi, ad birleştirmeyle farklı çağrılarda farklı adlar üretebilir; derleyici
  bu adların ortak bir kaynaktan geldiğini bilmez.
- Bir makro argümanını birden çok kez yerine koyabilir; yan etkili bir argüman bu yüzden
  gerçek bir işlevden farklı sayıda çalışabilir.
- Bir makro gövdesindeki parantezler öncelik sırasını korur, ama argümanın kaç kez
  yazıldığını değiştirmez — iki ayrı sorunun iki ayrı çözümüdür.

## Sonraki Adım

Bu dört ders boyunca hiçbir kod bloğu bir uyarı bırakmadı — ama bu bir tesadüf değil, elle
sağlanan bir tutarlılıktı: her makro dikkatle parantezlendi, her koşullu dal tutarlı bir
tipte tutuldu, her ad birleştirme geçerli bir tanımlayıcıyla sonuçlandı. Ön işlemci bu
tutarlılığın hiçbirini denetlemez; onu denetleyen, kendisinden bir sonraki aşamadır.
Sıradaki ders bu denetimin kendisine bakıyor: bir uyarının hangi aşamada doğduğu, bir
derleyici uyarı bayrağının neyi görünür kıldığı ve standardın hangi davranışı garanti
edip hangisini uygulamaya bıraktığı. Bu konunun son dersi burada bu kursun açılışında
sorulan soruya döner: dört aşamanın her biri kendi payına düşen hatayı nasıl görünür
kılar.
