---
title: 'Başlık Dosyaları'
source: 'https://academia.sh/tr/kurslar/c-temelleri/baslik-dosyalari'
course: 'C Temelleri'
language: tr
updated: '2026-09-13T05:07:32+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Bildirim ile tanım arasındaki farkı kaynak üzerinden ayırt eder'
    - 'Bir başlık dosyasının aynı bildirimi birden çok birime nasıl taşıdığını koşturarak gösterir'
    - 'Çifte içerme korumasının hangi hatayı engellediğini bir örnekle açıklar'
---

# Başlık Dosyaları

Bir başlık dosyası (header file) bir adı taşımaz, o adın metnini taşır ve ön işlemci onu her kullanan birime ayrı ayrı kopyalar. Bu, aynı bildirimin iki birime birden girmesini ve ikisinin de derlenmesini mümkün kılar; ölçüm bunu koşturarak, çifte içerme korumasının neyi engellediğini de ayrıca göstererek kanıtlıyor.

Önceki ders bağlayıcı sınıfını yalnız bir çağrıyla gösterdi: `kimlik`in bildirimi bir
dosyada, tanımı başka bir dosyadaydı ve aradaki köprüyü `asama.h` kurdu. Bu ders o köprünün
kendisine bakıyor. Bir başlık dosyası (header file) ne bir birimdir ne de kendi başına
derlenir; yalnız metin taşır ve o metin, onu `#include` eden her birime ayrı ayrı kopyalanır.

Bu tek cümle, kursun geri kalanında sık hatırlanacak bir gerçeği özetliyor: C'de bir birim
kendi kaynağıyla `#include` ettiği her şeyin **birleşimidir**. Derleyicinin gördüğü şey,
`asama.c` dosyasının ekranda göründüğü hâli değil, ön işlemcinin bütün `#include`leri
çözdükten sonra ürettiği tek parça metindir. Başlık dosyası bu birleşimin neresinde
durduğunu belirler ve bu ders o konumun neden yalnız bildirimlere ayrılması gerektiğini
gösteriyor.

## Bildirim ile Tanım

C'de bir adın kaynakta iki farklı görünümü vardır. **Bildirim (declaration)** bir adın var
olduğunu, tipini ve nasıl çağrılacağını söyler, ama bir gövde vermez. **Tanım (definition)**
ise o adın karşılığını — bir işlevin gövdesini, bir değişkenin ayrılmış yerini — verir. Bir
ad birden çok yerde bildirilebilir, ama yalnız bir yerde tanımlanabilir; bu ayrım, başlık
dosyalarının neden var olduğunun gerekçesidir.

Bir birim, çağırdığı her adın tanımını görmek zorunda değildir; yalnız bildirimini görmesi
yeter — bu, tek bir başlık dosyasının onlarca birim arasında paylaşılabilmesinin nedenidir. Derleyici bir çağrıyı üretmek için adın tipini, kaç parametre aldığını ve ne
döndürdüğünü bilmelidir — bunların hepsi bildirimde vardır. Adın gerçek karşılığının nerede
olduğu, derleyiciyi ilgilendirmeyen bir sorudur; o soruyu bağlayıcı yanıtlayacaktır. Bu
ayrım sayesinde bir birim, hiç görmediği bir tanımı çağırabilir — yeter ki doğru bir
bildirime sahip olsun.

```c
// asama.h — baska birimlerde tanimli adlarin bildirimi
#ifndef ASAMA_H
#define ASAMA_H

/* Bu iki islev baska bir birimde tanimli: cagrilari baglama asamasinda cozulur. */
int kimlik(void);
const char *birim_adi(void);

/* Bu degisken de baska bir birimde tanimli. */
extern int birim_sayaci;

#endif
```

`asama.h` üç adı **bildiriyor**, hiçbirini **tanımlamıyor**. `kimlik` ve `birim_adi`nin
gövdesi yok; `birim_sayaci`nin başında duran `extern` de tam olarak bunu söylüyor — "bu ad
başka bir yerde tanımlı, burada yalnız bildiriliyor." Tanımların kendisi ikinci birimde
duruyor.

```c
// secim.c — ikinci birim: tanimlar burada, cagrilar baglamada cozulur
#include "asama.h"

int birim_sayaci = 2;

int kimlik(void) {
    return 7;
}

const char *birim_adi(void) {
    return "secim";
}
```

Üçüncü dosya `asama.h`yi kendi başına, `secim.c`den habersiz olarak dahil ediyor ve üçünü de
çağırıyor.

```c
// asama.c — baslik dosyasinin tasidigi bildirimlerle ikinci birime baglanan kesit
#include <stdio.h>
#include "asama.h"

static void gozle(const char *ad, const char *sinif, const char *kanit) {
    printf("%-30s%-14s%s\n", ad, sinif, kanit);
}

int main(void) {
    char tampon[64];

    printf("%-30s%-14s%s\n", "gozlem", "asama", "kanit");

    gozle("baslik metni", "on islemci", "bildirimler kaynaga metin olarak kopyalandi");

    snprintf(tampon, sizeof tampon, "cagri %d verdi", kimlik());
    gozle("baska birimdeki islev", "baglayici", tampon);

    snprintf(tampon, sizeof tampon, "birim %s olarak bildirildi", birim_adi());
    gozle("baska birimdeki ad", "baglayici", tampon);

    snprintf(tampon, sizeof tampon, "sayac %d", birim_sayaci);
    gozle("baska birimdeki degisken", "baglayici", tampon);

    return 0;
}
```

```
gozlem                        asama         kanit
baslik metni                  on islemci    bildirimler kaynaga metin olarak kopyalandi
baska birimdeki islev         baglayici     cagri 7 verdi
baska birimdeki ad            baglayici     birim secim olarak bildirildi
baska birimdeki degisken      baglayici     sayac 2
```

**DD6.** `asama.h`nin kendisi hiçbir gözlemde `on islemci` etiketiyle görünmüyor; onun yerine
görünen `baslik metni` satırı bir **sonuçtur**, `asama.h`nin kendisi değil. Ön işlemcinin
yaptığı iş bir metin kopyalamaktan ibarettir: `#include "asama.h"` satırı, derleyici devreye
girmeden önce, `asama.h`nin içeriğiyle **değiştirilir**. `asama.c`de görünen `kimlik(void)`
bildirimi bu yüzden hiçbir zaman `asama.c`nin kaynağında elle yazılmadı — kopyalanarak oraya
geldi. Aynı satır `secim.c`nin kaynağına da kopyalandı; iki kopya birbirinden bağımsız,
ikisi de kendi biriminin bir parçası oldu. Bir dosyayı düzenlerken görünen `#include`
satırı, derlenen metnin yalnız bir özetidir — gerçek kaynak, ön işlemci çalıştıktan sonra
ortaya çıkan, gözle hiç görülmeyen genişletilmiş metindir.

`secim.c` de `asama.h`yi dahil ediyor ve aynı üç bildirim onun kaynağına da kopyalanıyor.
İki birim de bu bildirimlerle derleniyor ve ikisi de uyarısız derleniyor — aynı bildirim iki
ayrı yere girdi ve ikisi de bundan rahatsız olmadı, çünkü bildirim bir söz verir, bir yer
ayırmaz. Tanım ise yalnız `secim.c`de bir kez var; `asama.c` üç adı da kullanıyor ama
hiçbirinin tanımını taşımıyor, tanımları bağlama aşamasında ödünç alıyor.

Çıktıdaki dört satır bu ödünç almanın nasıl işlediğini gösteriyor. `baslik metni` satırı
kaynağın kendisiyle ilgili bir gözlem, çünkü tek bir metnin bildirim taşıdığını kanıtlıyor —
ama önemli olan `asama.c`nin bu üç adı kullanabilmesi. `baska birimdeki islev` ve `baska
birimdeki ad` satırları `secim.c`nin işlev tanımlarına ulaşıyor; `baska birimdeki degisken`
satırı ise bir değişken tanımına ulaşıyor. Üçü de aynı köprüyü kullanıyor: `asama.h`deki
bildirim, `asama.c`ye "bu ad var, işte tipi" diyor; gerçek karşılığı yalnız bağlama sırasında
geliyor. `asama.c` tek başına derlenirken `birim_sayaci`nin değerinin `2` olduğunu bile
bilmiyor — bildiği yalnız böyle bir tam sayının bir yerde var olduğu.

## Başlık Neyi Paylaşır, Neyi Paylaşmaz

`asama.h`nin yalnız bildirim taşıması bir seçim değil, bir zorunluluktur. Bir başlık dosyası
birden çok birime dahil edilebildiği için, içine bir işlev **tanımı** konsaydı — gövdesiyle
birlikte — o gövde her onu dahil eden birime ayrı ayrı kopyalanırdı. `asama.h`yi hem
`asama.c` hem `secim.c` dahil ediyor; eğer `kimlik`in gövdesi orada dursaydı, iki birim de
aynı adı tanımlamış olur ve bağlama aşamasında "aynı ad iki kez tanımlı" çakışması çıkardı.
Tanımın başlıkta değil, yalnız bir birimde durması bu yüzden zorunludur — çifte içerme
koruması bunu tek bir birim **içinde** önler, ama bir tanımın birden çok birime yayılmasını
önlemez; onu önleyen şey, tanımı hiç başlığa koymamaktır.

Bu, `extern int birim_sayaci;` satırının neden `int birim_sayaci = 2;` olarak yazılmadığını
da açıklıyor. `extern` anahtar sözcüğü derleyiciye "burada yer ayırma, bu ad başka bir yerde
zaten var" der; onsuz yazılan bir değişken bildirimi aslında bir tanımdır ve bir yer ayırır.
Başlığa `extern`siz bir değişken konsaydı, onu dahil eden her birim kendi kopyasını
ayırırdı — `asama.c` ile `secim.c` aynı adı taşıyan ama farklı adreslerde duran iki ayrı
`birim_sayaci` elde ederdi, bu da bağlama aşamasında ya çakışma ya da sessiz bir
tutarsızlık doğururdu.

Bu kural, işlev bildirimleri için doğrudan geçerli değildir; bir işlev bildirimi zaten kendi
başına bir yer ayırmaz, yalnız `extern`li bir değişken bildirimiyle aynı rolü oynar. Kuralın
özü şu: bir başlığa konan her satır, onu okuyan **her** birim tarafından aynı şekilde
yorumlanmalıdır. Bir bildirim bu koşulu doğal olarak sağlar, çünkü kopyalandığı her yerde
aynı sözü verir; bir tanım sağlamaz, çünkü kopyalandığı her yerde ayrı bir yer talep eder.

## Çifte İçerme Koruması

Bir başlık dosyası aynı birime birden fazla yoldan girebilir — doğrudan, ya da başka bir
başlık dosyasının içinden. Bu, tek bir dosyanın iki kez `#include` edilmesi kadar açık bir
hata olmak zorunda değil; çoğu zaman iki ayrı başlık, üçüncü ortak bir başlığa aynı anda
bağımlı olduğunda kendiliğinden ortaya çıkar. Bir birim ikisini de dahil ettiğinde, ortak
başlık o birime iki kez girmiş olur. `asama.h`nin başındaki `#ifndef` / `#define` çifti tam
olarak bunu karşılamak için var. Aşağıdaki küçük örnek bunu, `asama.h`den bağımsız bir
başlıkla gösteriyor.

```c
// sinir.h — bir tip bildiren kucuk baslik; cifte icerme korumasi tasiyor
#ifndef SINIR_H
#define SINIR_H

typedef struct {
    int satir;
    int sutun;
} Konum;

#endif
```

```c
// giris.h — sinir.h'yi bir yoldan ceken ara baslik
#include "sinir.h"
```

```c
// cikis.h — sinir.h'yi baska bir yoldan ceken ikinci ara baslik
#include "sinir.h"
```

```c
// baglam.c — sinir.h iki ayri yoldan ayni birime giriyor
#include <stdio.h>
#include "giris.h"
#include "cikis.h"

int main(void) {
    Konum k = {3, 7};
    printf("konum: satir %d sutun %d\n", k.satir, k.sutun);
    printf("baslik iki yoldan cekildi, tip bir kez tanimlandi\n");
    return 0;
}
```

```
konum: satir 3 sutun 7
baslik iki yoldan cekildi, tip bir kez tanimlandi
```

**DD7.** `baglam.c` `sinir.h`yi hiç doğrudan dahil etmiyor — `giris.h` ve `cikis.h` üzerinden,
iki ayrı yoldan dolaylı olarak çekiyor. Ön işlemci her iki `#include`i de sırayla işler:
birincisinde `SINIR_H` henüz tanımlı olmadığı için `Konum` tipi kaynağa kopyalanır ve
`SINIR_H` tanımlanır; ikincisinde `#ifndef SINIR_H` artık yanlıştır ve `#endif`e kadar olan
her şey **atlanır** — `Konum` bir daha kopyalanmaz. Koruma olmasaydı `Konum` aynı birimde iki
kez tanımlanmış olurdu ve bu derlenmezdi. Buradaki yapı **etiketsizdir**: C, etiketsiz her
yapı bildirimini ayrı bir tip sayar, bir `typedef` adı ise ancak **aynı** tipe yeniden
bağlanabilir. İkinci kopya `Konum`u ikinci ve farklı bir anonim tipe bağlamaya çalışır, kural
çiğnenir. Tanım, bildirimin aksine, tekil olmak zorundadır.

Bu, `#ifndef` ailesinin bildirimle değil **tanımla** ilgili bir sorunu çözdüğünü gösteriyor.
`kimlik`in bildirimi iki birime kopyalanabiliyordu, çünkü bildirim çoğaltılabilir. `Konum`un
tanımı ise aynı birime iki kez kopyalanamaz, çünkü tanım tekildir — tıpkı bir işlevin gövdesi
gibi. Çifte içerme koruması, bir başlığın kendi içindeki tanımları bu tekillik kuralından
korur.

**DD8.** `SINIR_H` adının kendisinin özel bir anlamı yok; yalnız "bu başlık daha önce
kopyalandı mı" sorusuna yanıt veren bir işaretçi görevi görüyor. Ön işlemcinin gördüğü şey
bir dosya adı ya da bir yol değil, yalnız bu adın tanımlı olup olmadığıdır — `#define SINIR_H`
satırı çalıştığı andan itibaren, aynı birimdeki her sonraki `#ifndef SINIR_H` yanlış sonuç
verir. Koruma bu yüzden dosyanın kendisine değil, kaynağa bırakılan bu tek işarete dayanır;
adı çakışan başka bir başlık aynı işareti kullansaydı, koruma yine çalışır ama bu kez yanlış
başlığı dışarıda bırakırdı. Adın başlığa özgü ve tekil olması bu yüzden programcının
sorumluluğudur, dilin garantisi değildir.

`giris.h` ve `cikis.h`nin kendileri hiçbir yeni bildirim taşımıyor; ikisi de yalnız
`sinir.h`ye birer köprü. Bu, gerçek başlık ağaçlarında sık görülen bir örüntüyü küçük
ölçekte yansıtıyor: bir başlık kendi ihtiyaç duyduğu başka başlıkları dahil eder ve onu
kullanan birim, bu iç bağımlılıkların farkında olmak zorunda kalmaz. `baglam.c` `sinir.h`nin
var olduğunu bile bilmeden `Konum` tipine erişti — `giris.h` ve `cikis.h`nin ikisi de bu
bağımlılığı kendi başına taşıdı.

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

Başlık dosyası bir sözleşme değildir. `asama.h`deki bildirim `secim.c`deki tanımla birebir
örtüşüyor çünkü bu çekirdekte ikisi elle tutarlı tutuldu — ama dilin kendisi bu tutarlılığı
denetlemez. `asama.h` `int kimlik(void);` yerine yanlışlıkla `int kimlik(int);` bildirseydi,
`secim.c`deki tanım hâlâ `int kimlik(void) { return 7; }` olurdu. `asama.c`yi derleyen
derleyici yalnız `asama.h`deki bildirime bakar ve çağrıyı ona göre üretir; `secim.c`yi
derleyen derleyici de yalnız kendi tanımına bakar. İkisi de kendi biriminin içinde
tutarlıdır, ikisi de tek başına uyarısız derlenir — çünkü derleyici aynı anda iki birimi
göremez, bu kursun ilk dersinden beri bilinen bir sınır.

Uyuşmazlık ancak iki birim bir araya gelince, yani bağlama sırasında ortaya çıkabilir. Ama
klasik bir C bağlayıcısı bir adın hangi imzayla bildirildiğini değil, yalnız adın var olup
olmadığını denetler — sembol tablosunda `kimlik` adını arar, bulur, adresi eşler. İmzanın
uyuşup uyuşmadığı çoğu zaman bağlayıcının da göremediği bir şeydir; hata orada da yakalanmaz,
tanımsız davranışa düşer. Çekirdekte durum böyle değildir: `asama.h`deki bildirim ile
`secim.c`deki tanım birebir örtüşüyor ve bu dersteki iki koşum da bunu zaten kanıtladı — hem
`kimlik` çağrısı hem `Konum` yapısı beklenen değerleri verdi. Başlık dosyası bu tutarlılığı
sağlamaz, yalnız onu **elle sağlanmışsa** taşır.

Bu sınır, önceki dersin ölçtüğü "aşamalar geri dönmez" bulgusunun bir başka görünümü. Ön
işlemci bir başlığı kopyalarken metnin doğru olup olmadığını sormaz, yalnız kopyalar.
Derleyici kopyalanan metni kendi biriminin içinde tutarlı bulduğu sürece bir şey söylemez.
Uyumsuzluk, iki birim ancak bağlanınca — ve o zaman bile yalnız bazı durumlarda — görünür
hale gelir. Bu yüzden bir başlık dosyasının doğruluğu, dilin denetlediği bir şey değil,
onu yazanın kendi disipliniyle koruduğu bir şeydir: bildirim değişince tanım da değişmeli,
ikisi ayrı dosyalarda dursa bile.

Bu disiplinin nasıl korunacağı ayrı bir konu — derleme sistemleri ve bağımlılık takibi
sonraki müfredatların konusu — ama bu dersin sınırı şunu söylüyor: `asama.h` var olduğu
için `kimlik`in imzası otomatik olarak doğru kalmıyor, yalnız **tek bir yerden**
değiştirilebiliyor. Başlık dosyasının asıl kazandırdığı şey doğruluk garantisi değil, bu
tekillik; bir bildirim değiştiğinde onu kullanan bütün birimlerin aynı metni görmesi.

## Özet

- Bir ad kaynakta iki ayrı biçimde görünür: bildirim (declaration) yalnız var olduğunu
  söyler, tanım (definition) gövdesini ya da yerini verir.
- Bir bildirim birden çok birime kopyalanabilir; bir tanım yalnız bir birimde bulunabilir.
- Başlık dosyası bir birim değildir; yalnız metin taşır ve onu dahil eden her birime ayrı
  ayrı kopyalanır.
- Çifte içerme koruması (`#ifndef` / `#define`) aynı başlığın bir birime birden fazla
  yoldan girip bir tanımı iki kez kopyalamasını engeller.
- Başlık dosyası bir sözleşme değildir: bildirim ile tanım uyuşmazsa derleyici bunu
  göremez, çünkü tek seferde tek birime bakar; bağlayıcı da yalnız adı arar, imzayı değil.
- Bir başlığa tanım değil bildirim konması bir üsluptan çok bir zorunluluktur: aynı tanım
  onu dahil eden her birime kopyalanırsa, bu birimler bağlanırken çakışma doğar.

## Sonraki Adım

Bu ders `baslik metni` satırının arkasındaki mekanizmayı açtı: ön işlemci bir dosyayı
kopyalıyor, ama kaynağın geri kalanı bu kopyalamanın yapıldığını hiçbir şekilde bilmiyor.
Kaynakta duran `#include "asama.h"` satırı ile derleyicinin gördüğü genişletilmiş metin
arasındaki fark, ön işlemcinin bütün kararlarının ortak özelliği: hiçbiri kaynakta iz
bırakmaz, yalnız sonucuyla var olur. Sıradaki ders ön işlemcinin geri kalan üç kararına
dönüyor — makro değeri, koşullu gövde ve ad birleştirme — ve her birinin metin düzeyinde
neden derleyiciden tamamen bağımsız çalıştığını, önceki dersten daha derin bir bakışla
gösteriyor.
