---
title: 'Bilgisayarlar Nasıl Çalışır'
source: 'https://academia.sh/tr/kurslar/bilgisayarlar-nasil-calisir'
language: tr
updated: '2026-08-17T04:39:35+00:00'
license: 'CC BY-SA 4.0'
lessons: 16
outcomes:
    - 'Bir programın kaynak metinden işlemci komutlarına dönüşüm zincirini açıklar'
    - 'Sayıların, metnin ve gerçel değerlerin bellekte nasıl temsil edildiğini gösterir'
    - 'Bellek hiyerarşisinin program başarımı üzerindeki etkisini örnekle açıklar'
---

# Bilgisayarlar Nasıl Çalışır

Verinin bellekteki temsilinden işlemcinin komut yürütmesine ve kaynak kodun çalıştırılabilir programa dönüşmesine uzanan, önkoşulsuz başlangıç kursu.

## Sayı Sistemleri ve Veri Temsili

Bitlerin sayıya, metne ve gerçel değere dönüştüğü temsil kurallarını kuran bölüm.

### İkilik Sayı Sistemi

Source: https://academia.sh/tr/kurslar/bilgisayarlar-nasil-calisir/ikilik-sayi-sistemi

Konum değerine dayalı sayı sistemleri, ikilik gösterim ve tabanlar arası dönüşüm.

Bir bilgisayarın belleğinde duran her şey — bu ders metni, bir fotoğraf, çalışan
programın kendisi — aynı türden bir nesnedir: iki durumlu anahtarlardan oluşan bir
dizi. Bu dersin sorusu şudur: iki durumdan başka bir şey tutamayan bir ortamda sayılar
nasıl yazılır?

Yanıt, aslında günlük hayatta kullanılan sayı sisteminin daha genel bir kuralının özel
bir hâlidir. Bu yüzden ikilik sisteme geçmeden önce, onluk sistemin ne yaptığına
dikkatle bakmak gerekir.

#### Konum Değeri

Onluk sistemde `4703` yazıldığında dört ayrı simge yan yana konur, ama ifade edilen
değer bu simgelerin toplamı değildir. Her simgenin katkısı, bulunduğu konuma bağlıdır:

$$
4703 = 4 \times 10^3 + 7 \times 10^2 + 0 \times 10^1 + 3 \times 10^0
$$

Bu düzene **konum değerli gösterim (positional notation)** denir. Üç bileşeni vardır:
bir **taban (base)** $b$, $0$'dan $b-1$'e kadar $b$ adet **rakam** ve sağdan sola
artan üsler. Genel olarak, rakamları $d_{n-1} d_{n-2} \dots d_1 d_0$ olan bir sayının
değeri şudur:

$$
\sum_{i=0}^{n-1} d_i \times b^{i}
$$

Onluk sistemin özel bir yanı yoktur; tabanın on olması, elde on parmak bulunmasının
tarihsel bir sonucudur. Kural her taban için aynı işler. Taban ikiye indirildiğinde
rakam kümesi $\{0, 1\}$'e daralır — ve bu, iki durumlu bir donanımın doğrudan
karşılığıdır.

#### İkilik Gösterim

**İkilik (binary)** sistemde taban $2$, rakamlar $0$ ve $1$'dir. Tek bir ikilik rakama
**bit** denir; sözcük "binary digit" ifadesinin kısaltmasıdır. Konum değerleri ikinin
kuvvetleridir:

$$
1011_2 = 1 \times 2^3 + 0 \times 2^2 + 1 \times 2^1 + 1 \times 2^0 = 8 + 0 + 2 + 1 = 11
$$

Alt simge, sayının hangi tabanda yazıldığını bildirir. Bu belirtme gereklidir: `1011`
dizisi onluk okunursa bin on bir, ikilik okunursa on bir eder. Aynı simge dizisinin
farklı tabanlarda farklı değerler taşıması, ilerideki derslerde daha sert bir biçimde
karşınıza çıkacaktır — aynı bit dizisi, yorumlayana göre bir sayı, bir harf veya bir
komut olabilir.

İlk on ikilik sayı, karşılıkları ile birlikte:

| Onluk | İkilik | Onluk | İkilik |
|---|---|---|---|
| 0 | 0 | 5 | 101 |
| 1 | 1 | 6 | 110 |
| 2 | 10 | 7 | 111 |
| 3 | 11 | 8 | 1000 |
| 4 | 100 | 9 | 1001 |

Bir örüntü göze çarpar: sayı her ikinin kuvvetine ulaştığında bir basamak daha gerekir.
Onluk sistemde `9`'dan `10`'a geçerken olan şeyin aynısı, ikilik sistemde `1`'den
`10`'a geçerken olur.

#### İkilikten Onluğa

İkilik bir sayıyı onluğa çevirmek, tanımı doğrudan uygulamaktır: bitlerin konum
değerlerini toplayın. Değeri $1$ olan bitlerin konum değerlerini toplamak yeterlidir,
çünkü $0$ olanların katkısı yoktur.

$$
110101_2 = 32 + 16 + 4 + 1 = 53
$$

Burada sağdan sola konum değerleri $1, 2, 4, 8, 16, 32$'dir; değeri $1$ olan bitler
$0.$, $2.$, $4.$ ve $5.$ konumlardadır.

#### Onluktan İkiliğe

Ters yön için tekrarlı bölme kullanılır. Sayıyı ikiye bölün, kalanı yazın, bölümle
devam edin; bölüm sıfıra ulaşınca kalanları **ters sırada** okuyun.

$53$ için:

| Bölme | Bölüm | Kalan |
|---|---|---|
| $53 \div 2$ | 26 | 1 |
| $26 \div 2$ | 13 | 0 |
| $13 \div 2$ | 6 | 1 |
| $6 \div 2$ | 3 | 0 |
| $3 \div 2$ | 1 | 1 |
| $1 \div 2$ | 0 | 1 |

Kalanlar aşağıdan yukarı okunduğunda $110101_2$ elde edilir; bir önceki bölümde
hesaplanan değerle tutarlıdır.

Yöntemin neden çalıştığı, konum değeri tanımına bakılınca görülür. Bir sayının ikiye
bölümünden kalan, o sayının en sağdaki bitidir: çift sayıların son biti $0$, tek
sayıların son biti $1$'dir. Bölüm alındığında sayı bir basamak sağa kaydırılmış olur ve
aynı soru bir sonraki bit için sorulur.

#### Bit, Bayt ve Değer Sayısı

Tek bir bit iki durum ayırt eder. İki bit dört, üç bit sekiz durum ayırt eder. Genel
olarak $n$ bit ile

$$
2^n
$$

farklı örüntü yazılabilir. Bu, temsil kapasitesinin üst sınırıdır ve donanımdan
bağımsız bir sayma gerçeğidir: 8 bitle 256 farklı örüntü vardır, 257'nci örüntü yoktur.

Sekiz bitlik gruba **bayt (byte)** denir. Bayt, belleğin adreslenebilen en küçük
birimidir; işlemci tek bir biti değil, en az bir baytı okur. Bir bayt işaretsiz tam sayı
olarak yorumlandığında $0$ ile $255$ arasındaki değerleri taşır: en küçük örüntü
`00000000`, en büyüğü `11111111` yani $2^8 - 1 = 255$.

Daha genel olarak, $n$ bitlik işaretsiz bir tam sayının aralığı $0$ ile $2^n - 1$
arasıdır. Aradaki $-1$, sıfırın da bir örüntü harcamasındandır. Yaygın genişlikler ve
sınırları:

| Genişlik | Örüntü sayısı | İşaretsiz en büyük değer |
|---|---|---|
| 8 bit | 256 | 255 |
| 16 bit | 65.536 | 65.535 |
| 32 bit | 4.294.967.296 | 4.294.967.295 |
| 64 bit | $\approx 1{,}8 \times 10^{19}$ | $2^{64} - 1$ |

Bu tablo ilerideki derslerin sık başvuracağı bir sınırı içerir. Bir değişkenin genişliği,
o değişkenin tutabileceği en büyük değeri belirler; sınır aşıldığında sayı büyümez,
temsil bozulur. Taşma (overflow) davranışı, işaretli sayıları ele alan derste
ayrıntılandırılacaktır.

#### Dönüşümü Programla Görmek

Aşağıdaki program, bu derste elle yapılan iki dönüşümü yürütür ve sonuçlarını yerleşik
işlevlerle karşılaştırır.

```python
def onluktan_ikilige(sayi: int) -> str:
    """Negatif olmayan bir tam sayının ikilik gösterimini döndürür."""
    if sayi == 0:
        return "0"
    bitler = []
    while sayi > 0:
        bitler.append(str(sayi % 2))   # kalan: en sağdaki bit
        sayi //= 2                     # bölüm: bir basamak sağa kaydırma
    return "".join(reversed(bitler))


def ikilikten_onluga(gosterim: str) -> int:
    """İkilik gösterimi konum değerlerini toplayarak çözer."""
    deger = 0
    for bit in gosterim:
        deger = deger * 2 + int(bit)
    return deger


print(onluktan_ikilige(53))          # 110101
print(ikilikten_onluga("110101"))    # 53
print(bin(53), int("110101", 2))     # 0b110101 53
```

`ikilikten_onluga` işlevindeki `deger * 2 + int(bit)` satırı, konum değeri tanımının
soldan sağa okunan biçimidir: her yeni bit, o ana kadar birikmiş değeri bir basamak
sola kaydırır ve kendi katkısını ekler.

Python'un yerleşik `bin` işlevi ikilik gösterimi `0b` önekiyle verir; `int` işlevinin
ikinci parametresi ise okunacak tabanı belirtir. Bu önek yalnızca bir yazım
sözleşmesidir, sayının kendisinin parçası değildir.

#### Özet

- Konum değerli gösterimde bir rakamın katkısı, taban üssü ile konumundan belirlenir;
  onluk sistemin ayrıcalığı yoktur.
- İkilik sistemde taban $2$, rakam kümesi $\{0, 1\}$'dir; tek bir ikilik rakama bit denir.
- İkilikten onluğa geçiş, değeri $1$ olan bitlerin konum değerlerini toplamaktır;
  ters yön tekrarlı bölmenin kalanlarını ters sırada okumaktır.
- $n$ bit ile tam olarak $2^n$ farklı örüntü yazılabilir; işaretsiz yorumda en büyük
  değer $2^n - 1$'dir.
- Sekiz bitlik gruba bayt denir ve bellek bayt düzeyinde adreslenir.

#### Sonraki Adım

İkilik gösterim donanıma sadıktır, ancak insan gözü için elverişsizdir: 32 bitlik tek
bir değer, hata yapmadan okunması güç bir sıfır–bir dizisidir. Sonraki ders, aynı bit
örüntüsünü daha az simgeyle ve bit sınırlarını bozmadan yazmayı sağlayan onaltılık
tabanı ele alacak; o dersle birlikte, kurs boyunca tekrar tekrar başvurulacak somut bir
örnek de kurulacaktır.

### Onaltılık ve Sekizlik Tabanlar

Source: https://academia.sh/tr/kurslar/bilgisayarlar-nasil-calisir/onaltilik-ve-sekizlik-tabanlar

Bit sınırlarını bozmadan kısa yazım sağlayan onaltılık ve sekizlik gösterimler ve aralarındaki dönüşümler.

Önceki ders, ikilik gösterimin donanımla birebir örtüştüğünü kurdu. Bu örtüşmenin
bedeli uzunluktur: 32 bitlik bir değer, `01000001010000100100001101000100` gibi otuz iki
simgelik bir dizidir ve bu diziyi hatasız okumak, yazmak veya karşılaştırmak insan için
elverişsizdir.

Bu dersin sorusu şudur: bit yapısını gizlemeden aynı değeri daha az simgeyle nasıl
yazabiliriz? Yanıt, taban seçimini ikinin bir kuvvetiyle sınırlamaktan geçer.

#### Neden İkinin Kuvveti Olan Tabanlar

Taban $2^k$ olarak seçilirse, o tabandaki her rakam tam olarak $k$ bite karşılık gelir.
Bu, dönüşümü hesaplama olmaktan çıkarıp gruplamaya indirger: bitleri $k$'lık öbeklere
ayırmak ve her öbeği tek bir rakamla değiştirmek yeterlidir.

Onluk sistemde bu geçerli değildir, çünkü $10$ ikinin kuvveti değildir. `4703` sayısının
onluk rakamlarına bakarak bit örüntüsü hakkında bir şey söylenemez; dönüşüm için bölme
yapmak gerekir. Onaltılık ve sekizlik sistemler bu nedenle tercih edilir: kısalık
sağlarlar ve bunu bit sınırlarını bozmadan yaparlar.

#### Onaltılık Sistem

**Onaltılık (hexadecimal)** sistemde taban $16$'dır. On altı rakam gerekir; onluk
sistemin on simgesi yetmediğinden, kalan altı değer için `A`–`F` harfleri kullanılır:

| Onaltılık | Onluk | İkilik | Onaltılık | Onluk | İkilik |
|---|---|---|---|---|---|
| 0 | 0 | 0000 | 8 | 8 | 1000 |
| 1 | 1 | 0001 | 9 | 9 | 1001 |
| 2 | 2 | 0010 | A | 10 | 1010 |
| 3 | 3 | 0011 | B | 11 | 1011 |
| 4 | 4 | 0100 | C | 12 | 1100 |
| 5 | 5 | 0101 | D | 13 | 1101 |
| 6 | 6 | 0110 | E | 14 | 1110 |
| 7 | 7 | 0111 | F | 15 | 1111 |

$16 = 2^4$ olduğundan her onaltılık rakam tam olarak dört bit taşır. Dört bitlik gruba
**yarım bayt (nibble)** denir. Bir bayt sekiz bit, yani iki yarım bayttır; dolayısıyla
her bayt tam olarak iki onaltılık rakamla yazılır. Bu, onaltılık gösterimin yaygın
kullanımının asıl nedenidir: bayt sınırları gösterimde görünür kalır.

Harflerin büyük veya küçük yazılması değeri değiştirmez; `0xFF` ile `0xff` aynı değeri
gösterir. Bir metinde tutarlılık için tek bir biçim seçilir.

#### Gruplayarak Dönüşüm

İkilikten onaltılığa geçmek için bitler **sağdan başlayarak** dörderli gruplanır ve her
grup tablodaki karşılığıyla değiştirilir. Soldaki son grup dörtten az bit içeriyorsa
başına sıfır eklenir.

$$
\underbrace{0100}_{4}\ \underbrace{0001}_{1}\ \underbrace{0100}_{4}\ \underbrace{0010}_{2}\ \underbrace{0100}_{4}\ \underbrace{0011}_{3}\ \underbrace{0100}_{4}\ \underbrace{0100}_{4}
$$

Otuz iki bitlik dizi, sekiz onaltılık rakama iner: `41424344`. Ters yön aynı kuralın
tersidir: her onaltılık rakam dört bitle değiştirilir.

Sağdan gruplamak önemlidir. Soldan gruplanırsa, bit sayısı dördün katı değilken tüm
konum değerleri kayar ve sonuç yanlış çıkar.

#### Kursun Ortak Örneği

Yukarıda elde edilen değer, bu kurs boyunca tekrar tekrar başvurulacak örnektir:

$$
\texttt{0x41424344}
$$

Onluk karşılığını konum değerlerinden hesaplayabilirsiniz:

$$
4 \times 16^7 + 1 \times 16^6 + 4 \times 16^5 + 2 \times 16^4 + 4 \times 16^3 + 3 \times 16^2 + 4 \times 16^1 + 4 \times 16^0 = 1.094.861.636
$$

Bu, 32 bitlik örüntünün **işaretsiz tam sayı olarak** yorumudur. Aynı örüntü sonraki
derslerde başka türlü de yorumlanacak: işaretli bir tam sayı, bir gerçel sayı, dört
harflik bir metin parçası ve bellekteki bayt sırasına göre farklı görünen bir dizi
olarak. Bit örüntüsü tüm bu okumalarda aynı kalır; değişen tek şey, ona uygulanan yorum
kuralıdır.

Bu ayrım, verinin doğası hakkında dersin taşıdığı asıl fikirdir: bellekte "sayı" veya
"metin" diye bir şey yoktur; bit örüntüleri ve onlara uygulanan yorumlar vardır.

#### Sekizlik Sistem

**Sekizlik (octal)** sistemde taban $8$'dir; rakamlar $0$–$7$ arasındadır. $8 = 2^3$
olduğundan her sekizlik rakam üç bit taşır ve dönüşüm yine gruplamayla yapılır — bu kez
üçerli.

$$
\underbrace{111}_{7}\ \underbrace{101}_{5}\ \underbrace{101}_{5} = 755_8
$$

Üçlü gruplama bayt sınırıyla hizalanmadığı için sekizlik gösterim genel amaçlı
kullanımda onaltılığın gerisinde kalır: 8 bit, üçerli gruplandığında iki tam rakam ve
artan iki bit verir.

Sekizliğin sürdüğü yer, doğal birimi üç bit olan alanlardır. Unix türevi sistemlerde
dosya izinleri bunun standart örneğidir: okuma, yazma ve çalıştırma izinleri üç bitlik
bir grup oluşturur ve bu grup sahip, grup ve diğerleri için ayrı ayrı tekrarlanır.
`755` yazımı, `rwxr-xr-x` iznini kodlar; ilk rakam $7 = 111_2$ üç iznin de verildiğini,
diğer iki rakam $5 = 101_2$ yazma izninin kapalı olduğunu söyler. İzin modeli, Linux
müfredatında ayrıntılı ele alınır.

#### Yazım Sözleşmeleri

Bir sayının hangi tabanda yazıldığı, metinde olduğu gibi kodda da belirtilmelidir.
Yaygın önekler:

| Önek | Taban | Örnek | Değer |
|---|---|---|---|
| `0b` | 2 | `0b1011` | 11 |
| `0o` | 8 | `0o755` | 493 |
| `0x` | 16 | `0x1F` | 31 |
| (yok) | 10 | `31` | 31 |

Önekler sayının değerinin parçası değildir; yalnızca okunacak tabanı bildirirler. Aynı
değer dört farklı biçimde yazıldığında bellekte aynı bit örüntüsü oluşur.

Aşağıdaki program dönüşümleri ve biçimlendirmeyi gösterir:

```python
deger = 0x41424344

print(deger)                      # 1094861636
print(hex(deger))                 # 0x41424344
print(bin(deger))                 # 0b1000001010000100100001101000100
print(f"{deger:#010x}")           # 0x41424344  (en az 8 rakam, önekli)
print(f"{deger:032b}")            # 01000001010000100100001101000100

# Bayt bayt okuma: her bayt iki onaltılık rakamdır.
for kaydirma in (24, 16, 8, 0):
    bayt = (deger >> kaydirma) & 0xFF
    print(f"{bayt:#04x}", bayt)   # 0x41 65 / 0x42 66 / 0x43 67 / 0x44 68
```

`bin` çıktısının 32 değil 31 basamak olması dikkat çekicidir: yerleşik işlev baştaki
sıfırları yazmaz. Sabit genişlikli gösterim gerektiğinde biçimlendirme belirteci
(`032b`, `#010x`) kullanılır. Sabit genişlik, bellek içeriği incelenirken sütunların
hizalı kalması için gereklidir.

Döngüdeki `>>` ve `&` işleçleri, örüntüden belirli bir baytı ayıklar; bu işleçlerin
tanımları bit düzeyi işlemler dersinde verilecektir.

#### Özet

- Taban $2^k$ seçildiğinde tabanlar arası dönüşüm hesap değil, $k$'lık gruplama işidir.
- Onaltılık sistemde her rakam dört bit, dolayısıyla her bayt tam olarak iki rakamdır;
  bayt sınırları gösterimde görünür kalır.
- İkilikten onaltılığa geçerken gruplama sağdan başlar; eksik kalan sol grup sıfırla
  tamamlanır.
- Sekizlik sistemde her rakam üç bittir; bayt sınırıyla hizalanmadığı için kullanımı üç
  bitlik doğal alanlarla (örneğin dosya izinleri) sınırlıdır.
- `0b`, `0o`, `0x` önekleri değeri değil, okunacak tabanı bildirir.
- Kursun ortak örneği `0x41424344`, işaretsiz yorumda $1.094.861.636$ değerindedir.

#### Sonraki Adım

Buraya kadar bütün sayılar negatif olmayan kabul edildi. Oysa donanımda eksi işareti
tutacak ayrı bir yer yoktur; işaret de bit örüntüsünün içinde kodlanmalıdır. Sonraki
ders, bunun nasıl yapıldığını ve seçilen yöntemin toplama devresini neden
değiştirmediğini ele alacak.

### İşaretli Tam Sayılar ve İkiye Tümleyen

Source: https://academia.sh/tr/kurslar/bilgisayarlar-nasil-calisir/isaretli-tam-sayilar

Negatif sayıların bit örüntüsü içinde kodlanması, ikiye tümleyen gösterim ve taşma davranışı.

Önceki iki ders sayıları negatif olmayan kabul etti. Oysa donanımda eksi işaretini
tutacak fazladan bir yer yoktur: 32 bitlik bir yazmaçta yalnızca 32 bit vardır. İşaret
de değerin kendisiyle aynı örüntünün içinde kodlanmak zorundadır.

Bu dersin sorusu, bu kodlamanın nasıl yapılacağıdır. Birden çok yanıt mümkündür; ancak
donanım neredeyse tek bir yanıtta birleşmiştir ve bunun nedeni, o yöntemin toplama
devresini hiç değiştirmemesidir.

#### İki Sezgisel Yaklaşım ve Sorunları

**İşaret-büyüklük (sign-magnitude)** gösteriminde en soldaki bit işareti, kalan bitler
büyüklüğü taşır. Sekiz bitte $+5$ için `0000 0101`, $-5$ için `1000 0101` yazılır. Bu
gösterim insan sezgisine yakındır ve iki sorunu vardır.

Birincisi, sıfırın iki gösterimi olur: `0000 0000` ve `1000 0000`. İki örüntü aynı
değeri taşıdığında, eşitlik karşılaştırması bit karşılaştırmasından ibaret olmaktan
çıkar.

İkincisi ve daha ağırı: toplama artık düz toplama değildir. $5 + (-5)$ hesabı bit
düzeyinde yapıldığında `0000 0101 + 1000 0101 = 1000 1010` çıkar; bu örüntü işaret-
büyüklük yorumunda $-10$ demektir, oysa sonuç sıfır olmalıydı. Donanımın işaretleri
ayıklayıp büyüklükleri karşılaştırması ve çıkarma yapması gerekir.

**Bire tümleyen (one's complement)** gösteriminde negatif sayı, pozitifin tüm bitleri
ters çevrilerek elde edilir: $-5$ için `1111 1010`. Toplama düz toplamaya yaklaşır ancak
elde biti yeniden başa eklenmelidir; ayrıca sıfır yine iki gösterimlidir (`0000 0000` ve
`1111 1111`).

#### İkiye Tümleyen

**İkiye tümleyen (two's complement)** gösterimi bu iki sorunu da ortadan kaldırır.
Tanımı tek cümleyle verilebilir: en soldaki bitin konum değeri negatiftir. $n$ bitlik
bir örüntünün değeri şudur:

$$
-d_{n-1} \times 2^{n-1} + \sum_{i=0}^{n-2} d_i \times 2^{i}
$$

Sekiz bit için en soldaki bitin ağırlığı $-128$, diğerlerininki alışıldık biçimde
$+64, +32, \dots, +1$'dir. Örnekler:

| Örüntü | Hesap | Değer |
|---|---|---|
| `0000 0101` | $4 + 1$ | $5$ |
| `1111 1011` | $-128 + 64 + 32 + 16 + 8 + 2 + 1$ | $-5$ |
| `1111 1111` | $-128 + 127$ | $-1$ |
| `1000 0000` | $-128$ | $-128$ |
| `0111 1111` | $127$ | $127$ |

Sıfırın tek gösterimi vardır: `0000 0000`. En soldaki bit hâlâ işareti ele verir — $1$
ise sayı negatiftir — ama bu bit ayrı bir işaret alanı değil, değerin bir parçasıdır.

$n$ bitlik ikiye tümleyen aralığı şudur:

$$
[-2^{n-1},\ 2^{n-1} - 1]
$$

Aralık bakışımlı değildir: sekiz bitte $-128$ temsil edilir, $+128$ edilemez. Negatif
tarafta bir değer fazladır, çünkü sıfır pozitif tarafın bir örüntüsünü harcar.

#### Negatifi Üretmek

Bir sayının negatifini bulmak için iki adım yeterlidir: **tüm bitleri ters çevir, sonuca
bir ekle.**

$5$ için: `0000 0101` $\rightarrow$ ters çevir `1111 1010` $\rightarrow$ bir ekle
`1111 1011`. Sonuç, tablodaki $-5$ örüntüsüdür.

Aynı işlem $-5$'e uygulanınca $5$'e dönülür: `1111 1011` $\rightarrow$ `0000 0100`
$\rightarrow$ `0000 0101`. İşlem kendi tersidir.

Kuralın neden çalıştığı şöyle görülür: bir örüntü ile tersinin toplamı, tüm bitleri $1$
olan örüntüdür, yani $2^n - 1$. Buna bir eklenirse $2^n$ elde edilir; $n$ bitlik bir
yazmaçta $2^n$ değeri sıfıra karşılık gelir. Dolayısıyla $x$ ile "ters çevir ve bir
ekle" sonucu toplandığında sıfır çıkar — negatifin tanımı budur.

Tek istisna en küçük değerdir: $-128$ üzerinde işlem uygulandığında yine `1000 0000`
elde edilir. $+128$ aralığın dışında olduğundan, bu değerin negatifi sekiz bitte yoktur.

#### Toplama Neden Tek Devre

İkiye tümleyenin donanım açısından belirleyici üstünlüğü şudur: işaretli toplama,
işaretsiz toplamayla **aynı** işlemdir. Bitler toplanır, taşan elde biti atılır.

$5 + (-3)$ hesabı sekiz bitte:

```
  0000 0101      (5)
+ 1111 1101      (-3)
-------------
1 0000 0010      (elde biti atılır)
  0000 0010      (2)
```

Sonuç doğrudur ve bunun için işaretlere bakılmamıştır. Çıkarma da ayrı bir devre
gerektirmez: $a - b$ hesabı, $b$'nin negatifi üretilip toplanarak yapılır. Karşılaştırma
işlemleri de aynı toplayıcı üzerine kurulur.

Bu, gösterim seçiminin donanım karmaşıklığını doğrudan azalttığı somut bir örnektir.
Sezgiye en yakın gösterim (işaret-büyüklük) en pahalı devreyi, sezgiye en uzak gösterim
(ikiye tümleyen) en ucuz devreyi verir.

#### Taşma

Sabit genişlik, aralığın dışına çıkan sonuçların temsil edilememesi anlamına gelir.
Sonuç kaybolmaz; aralığın öbür ucundan geri döner.

**İşaretsiz yorumda** sekiz bitte $255 + 1$ hesabı `1111 1111 + 0000 0001 = 1 0000 0000`
verir; elde atıldığında geriye `0000 0000`, yani $0$ kalır. Sayı büyümemiş, başa
sarmıştır.

**İşaretli yorumda** $127 + 1$ hesabı `0111 1111 + 0000 0001 = 1000 0000` verir; bu
örüntü $-128$ demektir. İki pozitif sayının toplamı negatif çıkmıştır.

İki durum arasındaki fark, aynı bit işleminin farklı yorumlanmasından ibarettir.
Donanım her iki taşmayı da ayrı bayraklarla bildirir; hangisinin anlamlı olduğuna,
değerin işaretli mi işaretsiz mi yorumlandığını bilen program karar verir.

Taşmanın pratikteki sonucu, sayısal kodun sessizce yanlış sonuç üretebilmesidir. İki
büyük pozitif sayının toplamının negatif çıkması, döngü sayaçlarının başa sarması ve
dizi indekslerinin beklenmedik yerlere işaret etmesi bu sınıftandır. Bu davranışın
güvenlik boyutu, siber güvenlik müfredatında ayrıca ele alınır.

#### Genişletme

Bir değer daha geniş bir tipe taşınırken boş kalan sol bitler doldurulmalıdır. Değerin
korunması için doldurma kuralı yoruma bağlıdır:

- **İşaretsiz** değerlerde sol bitler sıfırla doldurulur (sıfır genişletme).
- **İşaretli** değerlerde sol bitler işaret bitinin kopyasıyla doldurulur (işaret
  genişletme). Sekiz bitlik `1111 1011` ($-5$), on altı bitte `1111 1111 1111 1011`
  olur ve değeri korunur.

Yanlış kural uygulanırsa değer sessizce değişir: sekiz bitlik $-5$ örüntüsü sıfır
genişletmeyle on altı bitte `0000 0000 1111 1011`, yani $251$ olur. Ters yönde, on altı
bitlik $-5$ örüntüsü işaretsiz yorumlanırsa $65.531$ okunur. Bu, farklı genişlikteki
tipler arasında dönüşüm yapan kodun tipik hata kaynaklarından biridir.

#### Ortak Örneğin İşaretli Yorumu

Kursun ortak örneği `0x41424344` örüntüsünün en soldaki biti $0$'dır — ilk onaltılık
rakam `4`, yani `0100`. Bu nedenle örüntü, 32 bitlik işaretli yorumda da işaretsiz
yorumdakiyle aynı değeri taşır: $1.094.861.636$.

Örüntünün ilk rakamı `8` olsaydı — yani `0x81424344` — en soldaki bit $1$ olur ve aynı
işlemler bambaşka bir değer verirdi:

$$
2.168.603.460 - 2^{32} = -2.126.363.836
$$

Bit dizisi ile onun anlamı arasındaki mesafeyi bundan iyi gösteren bir örnek yoktur:
tek bir bitin değişmesi, değeri iki milyarın üzerinde kaydırır.

#### Uygulamada Görmek

Python'un tam sayıları sabit genişlikte değildir; taşma olmaz, sayı büyür. Sabit
genişlikli donanım davranışını gözlemlemek için maskeleme kullanılır:

```python
def sabit_genislik(deger: int, bit: int = 8) -> int:
    """Değeri `bit` genişliğinde ikiye tümleyen yorumuna indirger."""
    maske = (1 << bit) - 1          # 8 bit için 0xFF
    deger &= maske                  # fazla bitleri at
    isaret_biti = 1 << (bit - 1)    # 8 bit için 0x80
    if deger & isaret_biti:
        deger -= 1 << bit           # en soldaki bitin ağırlığı negatiftir
    return deger


print(sabit_genislik(0b11111011))     # -5
print(sabit_genislik(127 + 1))        # -128   (işaretli taşma)
print(sabit_genislik(-1))             # -1
print((255 + 1) & 0xFF)               # 0      (işaretsiz başa sarma)

print(sabit_genislik(0x41424344, 32)) # 1094861636
print(sabit_genislik(0x81424344, 32)) # -2126363836
```

`sabit_genislik` işlevinin gövdesi, bu dersin tanımının doğrudan çevirisidir: önce
genişliğe sığmayan bitler atılır, sonra en soldaki bit için negatif ağırlık uygulanır.

#### Özet

- Sabit genişlikli bir gösterimde işaret, ayrı bir alanda değil, örüntünün içinde
  kodlanır.
- İkiye tümleyen gösterimde en soldaki bitin konum değeri negatiftir; $n$ bitin aralığı
  $[-2^{n-1}, 2^{n-1}-1]$'dir ve sıfırın tek gösterimi vardır.
- Bir sayının negatifi, tüm bitler ters çevrilip bir eklenerek üretilir; en küçük değer
  bu işlemin istisnasıdır.
- İşaretli toplama, işaretsiz toplamayla aynı devrede yapılır; ikiye tümleyenin tercih
  nedeni budur.
- Aralık aşıldığında sonuç kaybolmaz, öbür uçtan geri döner: işaretsizde $255 + 1 = 0$,
  işaretlide $127 + 1 = -128$.
- Genişletmede doldurma kuralı yoruma bağlıdır; işaretli değerlerde işaret biti
  kopyalanır.

#### Sonraki Adım

Tam sayılar, temsil edebildikleri aralık içinde kesindir: her değer ya vardır ya
yoktur. Kesirli sayılarda durum başkadır — sonsuz sayıda gerçel değeri sonlu sayıda
örüntüye sığdırmak gerekir. Sonraki ders, bunun nasıl yapıldığını ve $0{,}1 + 0{,}2$
toplamının neden tam olarak $0{,}3$ etmediğini ele alacak.

### Kayan Noktalı Sayılar

Source: https://academia.sh/tr/kurslar/bilgisayarlar-nasil-calisir/kayan-noktali-sayilar

Gerçel değerlerin sonlu bitle temsili, IEEE 754 alan yapısı ve kesinlik kaybının kaynağı.

Tam sayılarda temsil ya vardır ya yoktur: sekiz bitte $200$ yazılabilir, $300$
yazılamaz. Kesirli sayılarda durum daha rahatsız edicidir. $0$ ile $1$ arasında sonsuz
sayıda gerçel sayı vardır; sonlu bir bit kümesi bunların yalnızca sonlu bir alt kümesini
tutabilir. Geri kalanı temsil edilemez ve **en yakın temsil edilebilir değere yuvarlanır.**

Bu dersin sorusu, bu alt kümenin nasıl seçildiğidir. Seçim, hesaplama sonuçlarına
doğrudan yansır: bu dersin sonunda $0{,}1 + 0{,}2$ toplamının neden tam olarak $0{,}3$
etmediği, bir tuhaflık değil beklenen davranış olarak görünecektir.

#### Sabit Nokta ve Sınırı

Akla ilk gelen çözüm, ondalık ayracı sabit bir konuma yerleştirmektir. Sekiz bitin üstü
tam kısma, altı kesir kısmına ayrılabilir; kesir bitlerinin konum değerleri $2^{-1}$,
$2^{-2}$, $2^{-3}$ diye sürer:

$$
\texttt{0101{,}1010}_2 = 4 + 1 + 0{,}5 + 0{,}125 = 5{,}625
$$

Bu **sabit noktalı (fixed-point)** gösterimdir ve belirli alanlarda hâlâ kullanılır.
Sorunu, aralık ile çözünürlüğün birbirine kilitlenmesidir: kesire ayrılan her bit,
temsil edilebilen en büyük sayıyı yarıya indirir. Bir hesaplamada hem gezegenler arası
uzaklıkları hem de atom yarıçaplarını taşımak gerekiyorsa, sabit bir ayraç konumu iki
ihtiyacın ikisini birden karşılayamaz.

#### Bilimsel Gösterimin İkilik Karşılığı

Çözüm, ayracın konumunu değerin kendisiyle birlikte saklamaktır. Bilimsel gösterimde
$6{,}022 \times 10^{23}$ yazıldığında iki bilgi taşınır: anlamlı rakamlar ve büyüklük
mertebesi. İkilik karşılığı aynı yapıdadır:

$$
(-1)^{s} \times 1{,}m \times 2^{e}
$$

Burada $s$ işaret, $m$ kesir kısmı (**mantis**), $e$ ise üstür. Ayraç, üs değiştikçe
"kayar" — gösterimin adı buradan gelir.

Bir ayrıntı bit tasarrufu sağlar: sıfır dışındaki her sayı, üs uygun seçilerek
$1{,}\dots$ biçiminde yazılabilir. Baştaki $1$ her zaman var olduğuna göre saklanmasına
gerek yoktur; donanım onu **örtük** kabul eder. Böylece mantis alanının tamamı kesir
kısmına ayrılır ve bir bit bedavaya kazanılır.

#### IEEE 754 Alan Yapısı

Kayan noktalı gösterimin bit düzeyindeki ayrıntıları IEEE 754 standardında tanımlıdır.
İki yaygın genişlik:

| Biçim | Toplam | İşaret | Üs | Mantis | Üs kayması |
|---|---|---|---|---|---|
| binary32 (tek kesinlik) | 32 bit | 1 | 8 | 23 | 127 |
| binary64 (çift kesinlik) | 64 bit | 1 | 11 | 52 | 1023 |

Üs alanı işaretli sayı olarak değil, **kaydırmalı (biased)** saklanır: alanda duran
değerden sabit bir kayma çıkarılarak gerçek üs bulunur. binary32'de alanda $130$
duruyorsa gerçek üs $130 - 127 = 3$'tür. Bu düzenlemenin nedeni sıralamadır: kaydırmalı
üs sayesinde iki pozitif kayan noktalı sayı, bit örüntüleri işaretsiz tam sayı gibi
karşılaştırılarak sıralanabilir.

Üs alanının iki uç değeri özel anlamlara ayrılmıştır:

- Tüm bitler $0$: değer sıfır (mantis de sıfırsa) veya **normal altı (subnormal)** sayı.
  Normal altı sayılar, sıfıra çok yakın bölgede örtük $1$ kuralından vazgeçerek
  çözünürlüğü sürdürür.
- Tüm bitler $1$: mantis sıfırsa **sonsuz**, sıfır değilse **sayı değil (NaN)**.

Sonsuz ve NaN, taşan veya tanımsız işlemlerin sonucunu programı durdurmadan taşır.
$1/0$ sonsuz, $0/0$ ise NaN verir. NaN'ın ayırt edici özelliği, kendisine bile eşit
olmamasıdır; bu, bir değerin NaN olup olmadığını sınamanın standart yoludur.

#### Ortak Örneğin Kayan Noktalı Yorumu

Kursun ortak örneği `0x41424344`, binary32 olarak yorumlandığında alanlara şöyle
ayrılır:

$$
\underbrace{0}_{s}\ \underbrace{1000\,0010}_{\text{üs alanı}}\ \underbrace{100\,0010\,0100\,0011\,0100\,0100}_{\text{mantis}}
$$

- İşaret biti $0$ olduğundan sayı pozitiftir.
- Üs alanı $1000\,0010_2 = 130$; gerçek üs $130 - 127 = 3$.
- Mantis alanı $4.342.596$; kesir değeri $4.342.596 / 2^{23} = 0{,}517677783966064453125$.
  Örtük $1$ eklenince anlamlı kısım $1{,}517677783966064453125$ olur.

Değer:

$$
1{,}517677783966064453125 \times 2^{3} = 12{,}141422271728515625
$$

Aynı 32 bit, işaretsiz tam sayı olarak $1.094.861.636$, kayan noktalı sayı olarak
$12{,}141422271728515625$ okunur. Bit örüntüsü değişmemiştir; değişen, ona uygulanan yorum
kuralıdır. Bir programlama dilinde değişkenin **tipi**, tam olarak bu kuralı seçmek
içindir.

#### Hangi Sayılar Tam Temsil Edilir

Mantis ikilik bir kesirdir. Dolayısıyla tam temsil edilen değerler, paydası ikinin
kuvveti olan kesirlerdir: $0{,}5$, $0{,}25$, $0{,}75$, $3{,}125$ gibi. Bu tür sayılara
**ikilik kesir (dyadic rational)** denir.

$0{,}1$ bunlardan değildir. Onluk sistemde tek basamakla yazılan bu sayı, ikilik
sistemde devirli bir açılım verir:

$$
0{,}1_{10} = 0{,}0001100110011\overline{0011}\dots_2
$$

Devirli açılım sonlu mantise sığmaz; en yakın temsil edilebilir değere yuvarlanır.
binary64'te saklanan değer $0{,}1$ değil, ona çok yakın başka bir sayıdır. Aynısı
$0{,}2$ ve $0{,}3$ için de geçerlidir.

Bu, dilin veya donanımın kusuru değildir: $1/3$ sayısının onluk sistemde sonlu
basamakla yazılamaması ne kadar doğalsa, $1/10$ sayısının ikilik sistemde sonlu bitle
yazılamaması da o kadar doğaldır. Değişen, hangi paydaların "şanslı" olduğudur.

#### Toplamanın Sonucu

Yuvarlanmış iki değer toplandığında sonuç da yuvarlanır ve hatalar birikir:

```python
print(0.1 + 0.2)              # 0.30000000000000004
print(0.1 + 0.2 == 0.3)       # False
print(f"{0.1:.20f}")          # 0.10000000000000000555

import math
print(math.isclose(0.1 + 0.2, 0.3))   # True

import struct
(deger,) = struct.unpack(">f", bytes.fromhex("41424344"))
print(deger)                  # 12.141422271728516
```

`0.1 + 0.2 == 0.3` karşılaştırmasının yanlış sonuç vermesi, bir hata değil temsilin
doğrudan sonucudur. Kayan noktalı değerler bu nedenle **eşitlikle karşılaştırılmaz**;
aradaki farkın kabul edilebilir bir eşiğin altında olup olmadığına bakılır. `isclose`
gibi işlevler bu karşılaştırmayı göreli ve mutlak tolerans ile birlikte yapar.

`struct` modülü, dört baytlık ham veriyi belirtilen biçimde yorumlar; `">f"` biçim
dizgisi "büyük uçlu, tek kesinlikli kayan noktalı" demektir. Buradaki uç sırası
belirtiminin ne anlama geldiği, bu konunun son dersinin konusudur.

#### Kesinlik Sınırı

Mantis genişliği, taşınabilen anlamlı basamak sayısını belirler. binary32 yaklaşık 7
anlamlı ondalık basamak, binary64 yaklaşık 15–17 basamak taşır. Bu sınırın iki pratik
sonucu vardır.

Birincisi: çok büyük ve çok küçük değerler toplandığında küçük olan kaybolabilir. Büyük
bir sayıya, mantisin çözünürlüğünün altında kalan bir değer eklendiğinde sonuç
değişmez.

İkincisi: hesap sırası sonucu etkiler. Kayan noktalı toplama birleşme özelliği
taşımaz — $(a + b) + c$ ile $a + (b + c)$ farklı sonuç verebilir. Bu, tam sayılardan
alışılan cebirsel özelliklerin burada geçerli olmadığı anlamına gelir ve sayısal
yöntemlerin ayrı bir disiplin olmasının nedenlerinden biridir.

Para tutarları gibi ondalık kesinliğin sözleşme gereği olduğu alanlarda kayan noktalı
tipler kullanılmaz; onluk tabanlı sabit kesinlikli tipler ya da tam sayı olarak
saklanan en küçük birim (örneğin kuruş) tercih edilir.

#### Özet

- Sonlu bit, gerçel sayıların yalnızca sonlu bir alt kümesini tutar; geri kalanı en
  yakın temsil edilebilir değere yuvarlanır.
- Kayan noktalı gösterim değeri $(-1)^s \times 1{,}m \times 2^{e}$ biçiminde saklar;
  baştaki $1$ örtüktür ve bir bit kazandırır.
- IEEE 754 binary32'de alanlar 1 / 8 / 23 bittir ve üs $127$ kaydırmasıyla saklanır;
  üs alanının uç değerleri sıfır, normal altı sayı, sonsuz ve NaN için ayrılmıştır.
- Tam temsil edilen kesirler, paydası ikinin kuvveti olanlardır; $0{,}1$ bunlardan
  değildir.
- Kayan noktalı değerler eşitlikle değil, tolerans ile karşılaştırılır; toplama
  birleşme özelliği taşımaz.
- Aynı `0x41424344` örüntüsü tam sayı olarak $1.094.861.636$, binary32 olarak
  $12{,}141422271728515625$ okunur.

#### Sonraki Adım

Buraya kadar bit örüntüleri hep bir bütün olarak yorumlandı. Oysa programlar sık sık
tek tek bitlerle çalışır: bir bayrağı açmak, bir alanı ayıklamak, bir değeri iki katına
çıkarmak. Sonraki ders, bunları yapan bit düzeyi işleçleri tanımlayacak; önceki
derslerde kullanılan `>>` ve `&` işleçleri de orada gerekçesine kavuşacak.

### Bit Düzeyi İşlemler

Source: https://academia.sh/tr/kurslar/bilgisayarlar-nasil-calisir/bit-duzeyi-islemler

VE, VEYA, XOR, DEĞİL işleçleri, kaydırmalar ve maskeleme ile alan ayıklama.

Önceki dersler bit örüntülerini hep bir bütün olarak yorumladı: otuz iki bit birlikte
bir tam sayı ya da bir gerçel sayı oluşturuyordu. Programlar ise sık sık örüntünün
parçalarıyla çalışır — tek bir baytı ayıklamak, bir bayrağı açmak, bir alanı sıfırlamak
gibi.

Bu dersin konusu, bu parça işlemlerini yapan işleçlerdir. Bunlar donanımın en ucuz
işlemleri arasındadır ve düşük seviyeli kodun büyük bölümü bunlarla yazılır.

#### Mantıksal İşleçler

Dört temel işleç, bitler üzerinde tanımlı doğruluk tablolarıyla verilir. İşleçler
**bit bit** uygulanır: sonucun $i$'nci biti, girdilerin yalnızca $i$'nci bitlerine bağlıdır.

| $a$ | $b$ | $a \mathbin{\&} b$ (VE) | $a \mid b$ (VEYA) | $a \oplus b$ (XOR) |
|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 |
| 0 | 1 | 0 | 1 | 1 |
| 1 | 0 | 0 | 1 | 1 |
| 1 | 1 | 1 | 1 | 0 |

**DEĞİL** işleci tek girdilidir ve her biti ters çevirir: $\lnot 0 = 1$, $\lnot 1 = 0$.

Sekiz bitlik iki örüntü üzerinde:

```
  1100 1010        1100 1010        1100 1010
& 1010 0110      | 1010 0110      ^ 1010 0110
-----------      -----------      -----------
  1000 0010        1110 1110        0110 1100
```

Her işlecin ayırt edici bir kullanımı vardır:

- **VE**, istenmeyen bitleri sıfırlar: karşısında $0$ olan her bit silinir.
- **VEYA**, istenen bitleri açar: karşısında $1$ olan her bit kurulur.
- **XOR**, seçili bitleri ters çevirir; ayrıca "farklı mı?" sorusunun bit karşılığıdır.
  Bir sayının kendisiyle XOR'u sıfırdır, bu da aynı değerle iki kez XOR yapmanın
  başlangıç değerine döndüğü anlamına gelir.

Bu işleçlerin mantıksal `ve`/`veya` işleçleriyle karıştırılmaması gerekir. Mantıksal
işleçler tüm değeri doğru/yanlış olarak ele alır ve çoğu dilde kısa devre yapar; bit
düzeyi işleçler her biti bağımsız işler ve kısa devre yapmaz.

#### Kaydırma

**Sola kaydırma** (`<<`) tüm bitleri sola taşır, sağdan sıfır doldurur. Her kaydırma,
konum değerlerini iki katına çıkardığı için sayıyı ikiyle çarpar:

$$
\texttt{0000 0101} \ll 2 = \texttt{0001 0100} \qquad (5 \times 4 = 20)
$$

**Sağa kaydırma** (`>>`) bitleri sağa taşır ve sayıyı ikiye böler; kesirli kısım atılır.
Soldan hangi bitin dolduğu, değerin yorumuna bağlıdır:

- **Mantıksal kaydırma** soldan sıfır doldurur; işaretsiz değerler için doğrudur.
- **Aritmetik kaydırma** soldan işaret bitinin kopyasını doldurur; işaretli değerlerin
  işaretini korur.

Bu ayrım, önceki dersteki işaret genişletme kuralının aynısıdır. Python'da tam sayılar
işaretlidir ve `>>` aritmetik davranır: `-8 >> 1` sonucu `-4`, `-5 >> 1` sonucu `-3`'tür
(sonuç sıfıra değil, aşağı yuvarlanır).

Sabit genişlikli dillerde kaydırma sayısının veri genişliğine eşit veya daha büyük
olması tanımsız davranıştır; 32 bitlik bir değeri 32 kez kaydırmak taşınabilir bir
sonuç vermez. Bu, sessizce yanlış çalışan kodun bilinen kaynaklarından biridir.

#### Maskeleme

**Maske**, hangi bitlerle ilgilenildiğini belirten sabit bir örüntüdür. Dört temel işlem
maskeyle yapılır:

| Amaç | İşlem | Açıklama |
|---|---|---|
| Bit sınama | `deger & maske` | Sonuç sıfır değilse ilgili bit kuruludur |
| Bit kurma | `deger \| maske` | Maskede $1$ olan bitler açılır |
| Bit silme | `deger & ~maske` | Maskede $1$ olan bitler sıfırlanır |
| Bit ters çevirme | `deger ^ maske` | Maskede $1$ olan bitler ters çevrilir |

Bir alanı ayıklamak iki adımdır: alanı en sağa kaydır, sonra fazlasını maskele.
Kursun ortak örneğinden ikinci baytı almak için:

$$
(\texttt{0x41424344} \gg 16)\ \&\ \texttt{0xFF} = \texttt{0x42}
$$

Kaydırma miktarı, alanın sağdan kaç bit uzakta başladığını; maske ise alanın kaç bit
genişliğinde olduğunu söyler. `0xFF` sekiz bitlik, `0x0F` dört bitlik, `0x01` tek bitlik
bir alan seçer.

Aynı yöntem tersinden de çalışır: bir alana değer yazmak için önce alan silinir, sonra
yeni değer kaydırılıp VEYA ile yerleştirilir.

#### Bayrak Kümeleri

Birbirinden bağımsız açık/kapalı seçeneklerin her biri bir bite atanabilir. Bu düzen,
tek bir tam sayıda çok sayıda seçeneği taşır ve seçeneklerin birleştirilmesini,
sınanmasını ucuz kılar.

```python
OKUMA   = 0b100      # 4
YAZMA   = 0b010      # 2
CALISMA = 0b001      # 1

izin = OKUMA | YAZMA              # 0b110 = 6

print(bool(izin & YAZMA))         # True   — yazma izni var mı?
print(bool(izin & CALISMA))       # False

izin |= CALISMA                   # çalışma iznini ekle   -> 0b111
izin &= ~YAZMA                    # yazma iznini kaldır   -> 0b101
print(f"{izin:03b}")              # 101
```

Bu örnekteki üç bit, ikinci derste sekizlik gösterimle yazılan dosya izinlerinin ta
kendisidir: `0b101` örüntüsü `5`, yani `r-x` iznidir. Sekizlik gösterimin o alanda
tercih edilmesinin nedeni, üçlü bit gruplarının doğrudan tek rakama karşılık gelmesiydi.

#### Yaygın Deyimler

Bit işleçleriyle kurulan birkaç kalıp, kaynak kodda sık görülür:

- `x & (x - 1)` — en sağdaki kurulu biti siler. Sonuç sıfırsa $x$ ikinin kuvvetidir
  (yalnız bir biti kuruluydu). Aynı ifade döngüde kullanılarak kurulu bitler sayılır.
- `x & -x` — yalnızca en sağdaki kurulu biti bırakır. İkiye tümleyende negatif alma
  işleminin tanımından çıkar.
- `x << k` ve `x >> k` — ikinin kuvvetleriyle çarpma ve bölme. Derleyiciler bu dönüşümü
  zaten yapar; kaynakta kaydırma yazmak okunabilirliği düşürür ve başarım kazandırmaz.
- `x ^ maske` — seçili bitleri ters çevirme; `x ^ x` her zaman sıfırdır.

Aşağıdaki program, bu deyimlerin ikisini ve alan ayıklamayı bir arada gösterir:

```python
def ikinin_kuvveti_mi(x: int) -> bool:
    """Pozitif x, tam olarak bir bit kuruluysa ikinin kuvvetidir."""
    return x > 0 and (x & (x - 1)) == 0


def kurulu_bit_sayisi(x: int) -> int:
    """Her adımda en sağdaki kurulu biti silerek sayar."""
    sayac = 0
    while x:
        x &= x - 1
        sayac += 1
    return sayac


deger = 0x41424344
baytlar = [(deger >> k) & 0xFF for k in (24, 16, 8, 0)]

print([hex(b) for b in baytlar])        # ['0x41', '0x42', '0x43', '0x44']
print(ikinin_kuvveti_mi(64))            # True
print(ikinin_kuvveti_mi(48))            # False
print(kurulu_bit_sayisi(deger))         # 9
print(bin(deger).count("1"))            # 9  — aynı sonuç, farklı yol
```

`kurulu_bit_sayisi` işlevinin döngüsü, kurulu bit sayısı kadar döner; bit genişliği
kadar değil. `0x41424344` örüntüsünde dokuz bit kuruludur (`0x41` ve `0x42` ikişer,
`0x43` üç, `0x44` iki), dolayısıyla döngü otuz iki değil dokuz kez çalışır.

#### Özet

- Bit düzeyi işleçler her biti bağımsız işler; mantıksal işleçlerden farklı olarak
  değerin tamamını doğru/yanlış olarak ele almazlar.
- VE bit siler, VEYA bit kurar, XOR bit ters çevirir; bir değerin kendisiyle XOR'u
  sıfırdır.
- Sola kaydırma ikiyle çarpar; sağa kaydırma ikiye böler ve soldan doldurma kuralı
  değerin işaretli yorumlanıp yorumlanmadığına bağlıdır.
- Alan ayıklama iki adımdır: alanı sağa kaydır, ardından genişliği kadar maske uygula.
- Bağımsız seçenekler tek bir tam sayıda bayrak bitleri olarak taşınabilir; birleştirme
  VEYA, sınama VE ile yapılır.
- `x & (x - 1)` en sağdaki kurulu biti siler; ikinin kuvveti sınaması ve bit sayımı bu
  deyime dayanır.

#### Sonraki Adım

Sayılar için temsil kuralları tamamlandı. Metin ise farklı bir sorun çıkarır: harfler
doğal olarak sayı değildir; her karakterin bir sayıya eşlenmesi gerekir ve bu eşleme
üzerinde dünya çapında anlaşmak gerekir. Sonraki ders, bu anlaşmanın nasıl kurulduğunu
ve kod noktası ile bayt dizisi arasındaki farkın neden önemli olduğunu ele alacak.

### Karakter Kodlamaları

Source: https://academia.sh/tr/kurslar/bilgisayarlar-nasil-calisir/karakter-kodlamalari

ASCII'den Unicode'a karakter kümeleri, kod noktası ile bayt dizisi ayrımı ve UTF-8 kodlaması.

Sayılar için temsil kuralları donanımın yapısından türetilebiliyordu: konum değeri,
ikiye tümleyen ve kayan nokta, aritmetiğin gereklerine göre tasarlanmıştı. Metinde
böyle bir doğal zorunluluk yoktur. `A` harfinin hangi sayıya karşılık geleceği bir
tasarım kararıdır ve bu kararın, yazan ile okuyan taraflarca **paylaşılması** gerekir.

Bu dersin konusu, o paylaşımın nasıl kurulduğudur. İki ayrı soru vardır ve karıştırılmaları
yaygın hata kaynağıdır: hangi karakterin hangi numarayı aldığı ve o numaranın baytlara
nasıl yazıldığı.

#### Karakter Kümesi ve Kodlama

**Karakter kümesi (character set)**, hangi karakterlerin var olduğunu ve her birine
hangi numaranın verildiğini söyler. Bu numaraya **kod noktası (code point)** denir.

**Kodlama (encoding)**, kod noktasının baytlara nasıl yazıldığını söyler.

Küçük kümelerde bu iki katman çakışır: karakter sayısı 256'yı geçmiyorsa her kod noktası
tek bir bayta sığar ve ayrım görünmez olur. Küme büyüdüğünde ayrım zorunlu hâle gelir —
tek baytın taşıyamayacağı bir numarayı yazmanın birden çok yolu vardır ve hangisinin
kullanıldığı belirtilmelidir.

#### ASCII

ASCII, 7 bitlik bir karakter kümesidir: $2^7 = 128$ kod noktası tanımlar. Yerleşim
rastgele değildir, aritmetik kolaylıklar gözetilerek düzenlenmiştir:

| Aralık (onaltılık) | İçerik |
|---|---|
| `00`–`1F` | Denetim karakterleri (satır sonu, sekme, dosya sonu vb.) |
| `20` | Boşluk |
| `30`–`39` | `0`–`9` rakamları |
| `41`–`5A` | `A`–`Z` büyük harfleri |
| `61`–`7A` | `a`–`z` küçük harfleri |

Rakamların ardışık olması, bir rakam karakterinden sayısal değerine geçmeyi tek çıkarma
işlemine indirger: `'7' - '0' = 7`. Büyük ve küçük harflerin arasındaki fark tam olarak
`0x20`'dir, yani tek bir bit: `A` = `0x41` = `0100 0001`, `a` = `0x61` = `0110 0001`.
Harf büyütme ve küçültme, ASCII sınırları içinde tek bir bit işlemidir.

Kursun ortak örneği burada üçüncü yorumuna kavuşur. `0x41424344` örüntüsünün dört baytı
`0x41`, `0x42`, `0x43`, `0x44`'tür; ASCII karşılıkları:

$$
\texttt{0x41424344} \longrightarrow \texttt{"ABCD"}
$$

Aynı otuz iki bit, işaretsiz tam sayı olarak $1.094.861.636$, binary32 olarak
$12{,}141422271728515625$, metin olarak `ABCD` okunur. Bir dosyanın veya ağ paketinin
içeriğini doğru okumak, bu üç yorumdan hangisinin geçerli olduğunu bilmeyi gerektirir;
biçim tanımının işi budur.

#### Kod Sayfaları ve Bıraktıkları Sorun

ASCII yalnızca 128 kod noktası tanımladığından, sekiz bitlik baytın üst yarısı boş kalır.
Türkçedeki `ğ`, `ş`, `ı` gibi harfler bu boş yarıya yerleştirilmiştir — ancak her dil
topluluğu kendi yerleştirmesini yaptığı için birbiriyle uyumsuz **kod sayfaları** ortaya
çıkmıştır.

Sonuç, aynı baytın hangi kod sayfasıyla okunduğuna göre farklı harf vermesidir. Yanlış
kod sayfasıyla açılan metinde harflerin bozuk simgelere dönüşmesi bu uyumsuzluğun
belirtisidir. Daha ağır bir sınır da vardır: tek bayt en fazla 256 karakter taşır; çok
dilli tek bir belge, kod sayfası yaklaşımıyla yazılamaz.

#### Unicode

Unicode bu sorunu, karakter kümesini kodlamadan ayırarak çözer. Dünyadaki yazı
sistemlerinin karakterlerine tek bir numara uzayı içinde kod noktası atar; gösterimi
`U+` önekiyle ve onaltılık yazılır:

| Karakter | Kod noktası |
|---|---|
| `A` | U+0041 |
| `ç` | U+00E7 |
| `ğ` | U+011F |
| `漢` | U+6F22 |

Kod noktası uzayı U+0000 ile U+10FFFF arasındadır; bu, bir milyondan fazla kod noktası
demektir. İlk 128 kod noktası ASCII ile birebir aynıdır — bu, geriye dönük uyumluluğu
sağlayan bilinçli bir karardır.

Unicode bir kodlama değildir: `U+011F` numarasının baytlara nasıl yazılacağını söylemez.
Bunu kodlamalar söyler.

#### UTF-8

UTF-8, kod noktasının büyüklüğüne göre 1 ile 4 arasında bayt kullanır. Bayt yapısı, ilk
baytın önek bitlerinden okunur:

| Kod noktası aralığı | Bayt sayısı | Örüntü |
|---|---|---|
| U+0000 – U+007F | 1 | `0xxxxxxx` |
| U+0080 – U+07FF | 2 | `110xxxxx 10xxxxxx` |
| U+0800 – U+FFFF | 3 | `1110xxxx 10xxxxxx 10xxxxxx` |
| U+10000 – U+10FFFF | 4 | `11110xxx 10xxxxxx 10xxxxxx 10xxxxxx` |

Kod noktasının bitleri, `x` ile gösterilen yuvalara soldan sağa yerleştirilir. `ç`
(U+00E7 = `0000 0000 1110 0111`) iki bayt gerektirir; anlamlı bitleri `00011 100111`
olarak bölünür:

$$
\texttt{110}\underline{00011}\ \texttt{10}\underline{100111} = \texttt{0xC3\ 0xA7}
$$

Aynı kuralla `漢` (U+6F22) üç bayta yazılır: `0xE6 0xBC 0xA2`.

Tasarımın üç sonucu vardır:

1. **ASCII uyumluluğu.** U+007F'ye kadar olan kod noktaları tek bayta, ASCII ile birebir
   aynı değerlere yazılır. Yalnızca ASCII içeren bir belge, UTF-8 belgesi olarak da
   geçerlidir.
2. **Kendini eşleyebilme.** Devam baytları her zaman `10` ile başlar; bir baytın
   karakter başlangıcı mı yoksa devamı mı olduğu, tek baştaki bitlerden anlaşılır.
   Bozulmuş bir akışta bir sonraki karakter sınırı bulunabilir.
3. **Değişken uzunluk.** Bir karakterin kaç bayt tuttuğu içeriğe bağlıdır; bu, metin
   işlemede doğrudan sonuçlar doğurur.

UTF-16 ve UTF-32 alternatif kodlamalardır. UTF-32 her kod noktasını sabit dört bayta
yazar; erişimi doğrudandır ama yer tüketimi yüksektir. UTF-16 çoğu karakteri iki bayta
yazar, kalanları için **vekil çift (surrogate pair)** kullanır — yani o da değişken
uzunlukludur, sabit sanılması yaygın bir yanılgıdır.

#### Kod Noktası, Bayt ve Grafem

Değişken uzunluklu kodlamada "uzunluk" tek anlamlı bir kavram değildir. Üç ayrı sayım
vardır ve üçü de farklı olabilir:

- **Bayt sayısı** — dosyada veya ağda kapladığı yer.
- **Kod noktası sayısı** — Unicode numaralarının adedi.
- **Grafem sayısı** — kullanıcının "harf" olarak gördüğü birimler.

Üçüncü ayrımın kaynağı **birleştirici işaretlerdir**. `é` karakteri iki farklı biçimde
yazılabilir: tek kod noktası olarak U+00E9 veya `e` (U+0065) ile birleştirici aksan
işaretinin (U+0301) art arda gelmesiyle. İki biçim ekranda aynı görünür, kod noktası
sayıları farklıdır ve doğrudan karşılaştırıldıklarında eşit çıkmazlar. **Normalleştirme**
işlemi, karşılaştırma öncesinde iki biçimden birini seçerek bu farkı ortadan kaldırır.

Harf büyütme ve küçültmenin de dilden bağımsız olmadığı burada görülür. ASCII'de tek bit
olan işlem, Türkçede `i` ↔ `İ` ve `ı` ↔ `I` eşleşmelerini gerektirir; dil bilgisi
olmadan yapılan dönüşüm yanlış sonuç verir.

```python
metin = "ABCD"
print(metin.encode("utf-8"))          # b'ABCD'          — 4 bayt

turkce = "çağ"
print(turkce.encode("utf-8"))         # b'\xc3\xa7a\xc4\x9f'
print(len(turkce), len(turkce.encode("utf-8")))   # 3 5

print(ord("ç"), hex(ord("ç")))        # 231 0xe7        — kod noktası U+00E7
print(chr(0x6F22))                    # 漢

ayrisik = "e\u0301"                   # e + birleştirici aksan (U+0301)
tek = "\u00e9"                        # tek kod noktası (U+00E9)

print(ayrisik == tek)                 # False
print(len(ayrisik), len(tek))         # 2 1

import unicodedata
print(unicodedata.normalize("NFC", ayrisik) == tek)    # True
```

`len(turkce)` sonucunun $3$, kodlanmış hâlinin $5$ bayt olması, iki sayımın neden
karıştırılmaması gerektiğini gösterir. Sabit uzunluk varsayan bir dizgi işlemi — örneğin
"ilk üç baytı al" — çok baytlı bir karakteri ortasından böler ve geçersiz bayt dizisi
üretir.

#### Özet

- Karakter kümesi karakterlere numara (kod noktası) atar; kodlama o numaranın baytlara
  nasıl yazılacağını belirler. İki katman ayrıdır.
- ASCII 128 kod noktası tanımlar; rakamlar ve harfler ardışıktır, büyük–küçük harf farkı
  tek bittir. Kursun ortak örneği `0x41424344`, ASCII yorumunda `ABCD` metnidir.
- Tek baytlık kod sayfaları hem uyumsuzdur hem de 256 karakterle sınırlıdır; çok dilli
  metin bu yaklaşımla yazılamaz.
- Unicode tek bir kod noktası uzayı tanımlar ve ilk 128 kod noktasında ASCII ile
  örtüşür.
- UTF-8 kod noktasını 1–4 bayta yazar; ASCII ile uyumludur, devam baytları `10` ile
  başladığı için kendini eşleyebilir.
- Bayt sayısı, kod noktası sayısı ve grafem sayısı farklı büyüklüklerdir; birleştirici
  işaretler ve dile bağlı harf dönüşümleri bu farkı görünür kılar.

#### Sonraki Adım

Bir karakterin birden çok bayta yazıldığı anlaşıldı. Peki bu baytlar belleğe hangi
sırayla yerleşir? Sayılar için de aynı soru geçerlidir: `0x41424344` değerinin dört
baytı bellekte hangi sırada durur? Sonraki ders, bu sıranın donanıma göre değiştiğini ve
veri iki sistem arasında taşınırken neden bir sorun kaynağına dönüştüğünü ele alacak.

### Bayt Sırası

Source: https://academia.sh/tr/kurslar/bilgisayarlar-nasil-calisir/bayt-sirasi

Çok baytlı değerlerin bellekteki yerleşim sırası, küçük ve büyük uçlu gösterim, veri değişiminde doğurduğu sorunlar.

Buraya kadar bir değerin kaç bitten oluştuğu ve bu bitlerin nasıl yorumlandığı
kuruldu. Bir soru açıkta kaldı: bellek bayt düzeyinde adreslenir, oysa `0x41424344`
değeri dört bayttır. Bu dört bayt, ardışık dört adrese hangi sırayla yerleşir?

Sorunun tek bir doğru yanıtı yoktur. Donanım aileleri farklı yanıtlar vermiştir ve bu
farklılık, veri iki sistem arasında taşındığında sessiz bozulmalara yol açar. Bu ders,
farkı ve ona karşı alınan önlemi tanımlar.

#### İki Yerleşim

Değerin en anlamlı baytı (**most significant byte**) `0x41`, en az anlamlı baytı
(**least significant byte**) `0x44`'tür. İki yerleşim düzeni vardır:

**Büyük uçlu (big-endian)** düzende en anlamlı bayt en düşük adrese yazılır:

| Adres | $A$ | $A{+}1$ | $A{+}2$ | $A{+}3$ |
|---|---|---|---|---|
| Bayt | `41` | `42` | `43` | `44` |

**Küçük uçlu (little-endian)** düzende en az anlamlı bayt en düşük adrese yazılır:

| Adres | $A$ | $A{+}1$ | $A{+}2$ | $A{+}3$ |
|---|---|---|---|---|
| Bayt | `44` | `43` | `42` | `41` |

Değer her iki düzende de aynıdır: $1.094.861.636$. Değişen, o değerin bellekteki
fiziksel yerleşimidir. Adları, Gulliver'in Gezileri'nde yumurtanın hangi ucundan
kırılacağı üzerine çıkan tartışmaya yapılan bir göndermedir; adlandırma, tartışmanın
teknik önemi ile pratikte yarattığı yorgunluk arasındaki farkı iyi anlatır.

#### İki Düzenin Gerekçeleri

Küçük uçlu düzenin savunusu, genişlik dönüşümünün adres değiştirmemesidir. Küçük uçlu
bir sistemde 32 bitlik bir değerin bulunduğu adresten tek bayt okunursa, değerin en az
anlamlı baytı elde edilir; aynı adresten iki bayt okunursa alt on altı bit elde edilir.
Bir değeri daha dar bir tipe daraltmak, adres aritmetiği gerektirmez.

Büyük uçlu düzenin savunusu okunabilirlik ve sıralamadır. Bellek dökümü soldan sağa
okunduğunda baytlar, sayının yazıldığı sırayla görünür. Ayrıca çok baytlı değerler bayt
bayt karşılaştırıldığında, karşılaştırma sayısal sıralamayla örtüşür; sözlük sıralı
anahtar kullanan yapılar için bu bir kolaylıktır.

İki gerekçe de teknik olarak geçerlidir ve seçim büyük ölçüde tarihseldir. Önemli olan,
bir sistemin hangi düzeni kullandığının **bilinmesi** ve veri o sistemin dışına
çıkarken düzenin **açıkça sabitlenmesidir**.

#### Aynı Baytlar, Farklı Değerler

Sorunun somut hâli şudur: dört bayt `41 42 43 44` bir dosyadan okunduğunda hangi sayı
elde edilir?

- Büyük uçlu okumada: `0x41424344` = $1.094.861.636$
- Küçük uçlu okumada: `0x44434241` = $1.145.258.561$

İki değer arasında elli milyonun üzerinde fark vardır ve hiçbir hata belirtisi
oluşmaz; program çalışır, sonucu yanlıştır. Bu, önceki derslerin tekrarladığı ilkenin
son hâlidir: baytlar kendi anlamlarını taşımaz, anlam okuma kuralından gelir.

Metin tarafında durum farklıdır. ASCII ve UTF-8, tek baytlık birimlerden oluşan
dizilerdir; bir bayt dizisinin sırası kodlamanın kendisi tarafından belirlenmiştir, bu
yüzden bayt sırası sorunu doğmaz. `41 42 43 44` baytları UTF-8 olarak her sistemde
`ABCD` okunur.

Buna karşılık UTF-16 ve UTF-32'nin birimleri iki ve dört bayttır; bu kodlamalarda bayt
sırası belirtilmek zorundadır. Belirtme yolu, metnin başına **bayt sırası imi (byte
order mark)** olarak U+FEFF kod noktasının yazılmasıdır. UTF-8 metinlerde aynı imin
kullanılması gerekmez; bulunması, sırayı değil yalnızca kodlamayı ima eder ve bazı
ayrıştırıcılarda beklenmeyen davranışa yol açar.

#### Ağ Bayt Sırası

Ağ protokolleri, uçlardaki donanımın düzeninden bağımsız olmak zorundadır. Bu nedenle
protokol başlıklarındaki çok baytlı alanlar için tek bir düzen sabitlenmiştir: büyük
uçlu. Bu sözleşmeye **ağ bayt sırası (network byte order)** denir.

Program, konak düzeninden ağ düzenine dönüşümü açıkça yapar. Dönüşüm işlevleri, konak
düzeni zaten büyük uçluysa hiçbir şey yapmaz; küçük uçluysa baytları ters çevirir.
Kodun her iki donanımda da doğru çalışması, bu dönüşümün atlanmamasına bağlıdır.

Aynı disiplin dosya biçimleri için de geçerlidir: ikili bir biçim tanımlanırken alan
genişlikleri ve bayt sırası belgede yazılır. Bellek içeriğinin olduğu gibi diske
yazılması veya ağa gönderilmesi, biçimi üreten donanıma bağımlı bir dosya üretir.

#### Uygulamada

Python'da bayt sırası, dönüşüm işlevlerine açıkça bildirilir:

```python
import sys, struct

print(sys.byteorder)                      # çalışılan sistemin düzeni: 'little' veya 'big'

deger = 0x41424344

print(deger.to_bytes(4, "big").hex())     # 41424344
print(deger.to_bytes(4, "little").hex())  # 44434241

ham = b"ABCD"                             # 41 42 43 44 baytları
print(hex(int.from_bytes(ham, "big")))    # 0x41424344
print(hex(int.from_bytes(ham, "little"))) # 0x44434241

print(struct.pack(">I", deger))           # b'ABCD'
print(struct.pack("<I", deger))           # b'DCBA'
print(struct.unpack("!I", b"ABCD")[0])    # 1094861636  — '!' ağ bayt sırası
```

`struct.pack(">I", 0x41424344)` çağrısının `b'ABCD'` üretmesi, bu konunun bütün
derslerini tek satırda birleştirir: aynı otuz iki bit, tam sayı olarak
$1.094.861.636$, kayan noktalı olarak $12{,}141422271728515625$, metin olarak `ABCD`,
bellekte ise sisteme göre `41 42 43 44` veya `44 43 42 41` dizisidir.

Biçim dizgisindeki ilk karakter düzeni belirtir: `<` küçük uçlu, `>` büyük uçlu, `!`
ağ bayt sırası (büyük uçluya eşdeğer). Bu karakter yazılmazsa çalışılan sistemin düzeni
kullanılır — taşınabilir kodda kaçınılması gereken durum budur.

#### Bit Sırası ile Karıştırmamak

Uç sırası tartışması **baytlar** hakkındadır, bitler hakkında değil. Bir baytın içindeki
bitlerin konum değerleri her iki düzende de aynıdır: en anlamlı bit soldadır, en az
anlamlı bit sağdadır. Küçük uçlu bir sistemde `0x41` baytı yine `0100 0001` örüntüsüdür;
ters çevrilmez.

Bit sırası kavramı ayrı bir bağlamda geçerlidir: verinin tel üzerinden seri olarak
iletildiği donanım katmanlarında, bitlerin hangi uçtan gönderileceği protokole göre
değişir. Bu, yazılımın gördüğü bayt düzeyindeki sıradan bağımsız bir karardır ve ağ
müfredatının fiziksel katman konusunda ele alınır.

Üçüncü bir karışıklık kaynağı, bit alanlarının bellek yerleşimidir. Bir yapı içinde bit
genişliği belirtilen alanların hangi sırayla paketleneceği, bayt sırasına ek olarak
derleyiciye bağlıdır. Bu nedenle bit alanları, taşınabilir olması gereken ikili
biçimlerde kullanılmaz; alanlar açık maskeleme ve kaydırma ile üretilir — beşinci
dersteki işleçler tam olarak bu iş içindir.

#### Veri Değişimi Disiplini

Bayt sırası sorunundan kaçınmanın yolu, onu her sınırda açıkça ele almaktır:

1. **İkili biçimlerde düzen belgelenir.** Alan genişlikleri ve bayt sırası, biçim
   tanımının parçasıdır; okuyucu ve yazıcı aynı belgeye uyar.
2. **Dönüşüm sınırlarda yapılır.** Değer sisteme girerken konak düzenine, çıkarken
   sabitlenmiş düzene çevrilir; arada kalan kod düzeni hiç düşünmez.
3. **Bellek içeriği doğrudan taşınmaz.** Yapıların ham kopyası; bayt sırasının yanı
   sıra hizalama ve dolgu (padding) farkları nedeniyle de taşınabilir değildir.
4. **Metin biçimleri sorunu kendiliğinden çözmez.** JSON gibi metin tabanlı biçimlerde
   bayt sırası sorunu yoktur, ancak bu kez sayısal kesinlik ve kodlama kararları
   belgelenmelidir.

Bu disiplin, ağ protokolleri ve dosya biçimleriyle çalışan her katmanda tekrar
karşınıza çıkacaktır.

#### Özet

- Çok baytlı bir değerin baytları belleğe iki farklı sırayla yerleşebilir: küçük uçlu
  düzende en az anlamlı bayt, büyük uçlu düzende en anlamlı bayt en düşük adrestedir.
- Değer aynı kalır, yerleşim değişir; aynı bayt dizisi iki düzende farklı sayı verir.
- Küçük uçlu düzen genişlik daraltmayı adres aritmetiğinden kurtarır; büyük uçlu düzen
  bellek dökümü okunabilirliği ve sözlük sıralaması sağlar.
- Tek baytlık birimlerden oluşan kodlamalarda (ASCII, UTF-8) bayt sırası sorunu doğmaz;
  UTF-16 ve UTF-32'de sıra bayt sırası imiyle belirtilir.
- Ağ protokolleri büyük uçlu düzeni sabitler; dönüşüm program tarafından açıkça
  yapılır.
- İkili veri değişiminde bayt sırası biçim tanımının parçasıdır; ham bellek kopyası
  taşınabilir değildir.

#### Sonraki Adım

Bu konu, verinin bellekte nasıl temsil edildiğini kurdu: bitler, tam sayılar, gerçel
sayılar, metin ve bayt yerleşimi. Sırada verinin ne yapıldığı var. Sonraki konu,
belleğin işlemciye nasıl bağlandığını ve bir komutun donanımda hangi adımlarla
yürütüldüğünü ele alacak; orada `0x41424344` örüntüsü bir kez daha, bu kez bir komutun
kendisi olarak karşınıza çıkabilecek bir bit dizisi olarak görünecek.

## İşlemci ve Bellek

Komutların yürütülmesi, verinin işlemciye taşınması ve bellek hiyerarşisinin başarıma etkisi.

### Yazmaçlar ve Ana Bellek

Source: https://academia.sh/tr/kurslar/bilgisayarlar-nasil-calisir/yazmaclar-ve-ana-bellek

Belleğin adreslenen bir dizi olarak modeli, yazmaçların rolü ve erişim maliyeti farkı.

Önceki konu, verinin bit örüntüsü olarak nasıl temsil edildiğini kurdu. Bu bit
örüntüleri bir yerde durmak zorundadır ve durdukları yer, program başarımını doğrudan
belirler. Bu konu, o yerin yapısını ve verinin işlemciye nasıl taşındığını ele alır.

Bu dersin sorusu şudur: bellek bir programa nasıl görünür ve işlemci üzerinde işlem
yapacağı veriyi nerede tutar?

#### Bellek: Adreslenen Bir Dizi

Programın gördüğü bellek modeli sadedir: her biri bir bayt tutan, sıfırdan başlayarak
numaralanmış hücrelerden oluşan bir dizi. Hücrenin numarasına **adres**, içeriğine
**değer** denir.

| Adres | 1000 | 1001 | 1002 | 1003 | 1004 |
|---|---|---|---|---|---|
| Değer | `41` | `42` | `43` | `44` | `00` |

Bu modelin iki sonucu vardır. Birincisi, belleğe erişim iki bilgi gerektirir: hangi
adres ve kaç bayt. Önceki konudaki `0x41424344` değeri dört bayttır; bu değeri okumak,
`1000` adresinden başlayarak dört bayt istemek demektir. Kaç baytın hangi sırayla
yorumlanacağı, bayt sırası dersinde tanımlanan kuraldır.

İkincisi, adresin kendisi de bir sayıdır ve bellekte saklanabilir. Bir değişkenin
adresini tutan değişken — yani **işaretçi (pointer)** — bu gözlemin doğrudan
sonucudur. Adres genişliği, adreslenebilecek en büyük bellek miktarını belirler: 32
bitlik adresler $2^{32}$ farklı bayt, yani yaklaşık dört milyar hücre adresleyebilir;
64 bitlik adreslerin sınırı pratikte hiçbir donanımın ulaşamayacağı büyüklüktedir.

#### Yazmaçlar

İşlemci, üzerinde işlem yapacağı değerleri ana bellekte tutmaz. Aritmetik ve mantık
birimi yalnızca kendi içindeki küçük ve hızlı saklama birimlerinden okuyabilir; bunlara
**yazmaç (register)** denir.

Yazmaçların üç ayırt edici özelliği vardır:

- **Sayıları azdır.** Bir işlemcide genel amaçlı yazmaç sayısı onlarla ifade edilir;
  ana bellekteki hücre sayısı milyarlarla.
- **Adresleri yoktur.** Yazmaçlara numarayla değil, komutta doğrudan adlarıyla atıf
  yapılır.
- **Erişimleri neredeyse ücretsizdir.** Yazmaç okuma, işlemcinin bir saat çevrimi
  mertebesindedir.

Bazı yazmaçlar özel görevlidir: program sayacı bir sonraki komutun adresini, yığıt
işaretçisi çağrı yığıtının tepesini tutar. Bunların işlevi sonraki derslerde
tanımlanacaktır.

Bir toplama işlemi bu nedenle tek adım değildir. `a + b` hesabı için önce iki değer
bellekten yazmaçlara yüklenir, toplama yazmaçlar arasında yapılır, sonuç yeniden
belleğe yazılır. Yükle–işle–sakla düzenini **zorunlu** kılan mimariler RISC ailesidir;
x86 gibi mimarilerde bir komut işlenenlerinden birini doğrudan bellekten alabilir. Hesabın
yazmaçlarda yapılması ise iki ailede de değişmez.

#### Erişim Maliyeti Farkı

Yazmaç ile ana bellek arasındaki hız farkı, program tasarımını etkileyecek büyüklüktedir.
Kesin süreler donanıma göre değişir; değişmeyen, **mertebe farkıdır**:

| Katman | Erişim mertebesi (saat çevrimi) |
|---|---|
| Yazmaç | ~1 |
| Birinci düzey önbellek | birkaç |
| Son düzey önbellek | onlarca |
| Ana bellek | yüzlerce |
| Kalıcı depolama | on binlerce ve üzeri |

Her satır, bir öncekinden belirgin biçimde yavaş ve belirgin biçimde büyüktür. Bu
kademelenmeye **bellek hiyerarşisi** denir ve tasarımının nedeni ekonomiktir: hızlı
saklama pahalıdır, ucuz saklama yavaştır. Hiyerarşi, ikisinin arasında bir denge kurar.

Sonuç şudur: bir programın hızı, yaptığı işlem sayısı kadar, veriye nereden eriştiğine
de bağlıdır. Ana bellekten okunan tek bir değer, yüzlerce aritmetik işlem kadar süre
alabilir. Bu gözlem, sonraki derste ele alınacak önbelleğin var oluş nedenidir.

#### Saklama Ortamlarının Farkı

Hiyerarşinin katmanları yalnızca hız ve kapasiteyle değil, fiziksel çalışma
biçimleriyle de ayrılır.

Yazmaçlar ve önbellek, durumu besleme gerilimi sürdüğü sürece koruyan devrelerle
kurulur; erişim hızlıdır, birim başına maliyet yüksektir. Ana bellek ise durumu küçük
yüklerde tutar ve bu yükler zamanla boşaldığı için düzenli olarak **tazelenmek**
zorundadır. Tazeleme, ana belleğin hem daha yavaş hem de daha ucuz olmasının nedenidir:
hücre başına daha az devre öğesi kullanılır.

Bu üç katmanın ortak özelliği **uçuculuktur**: güç kesildiğinde içerik kaybolur. Kalıcı
depolama ise içeriği güç olmadan da korur, ancak erişimi mertebelerce yavaştır ve
yazma işlemleri genellikle blok düzeyinde yapılır.

Uçuculuk ayrımı, bir programın çalışırken tuttuğu verinin neden ayrıca kaydedilmesi
gerektiğini açıklar: bellekteki durum, sürecin ömrüyle sınırlıdır.

#### Sözcük Genişliği ve Hizalama

**Sözcük (word)**, işlemcinin doğal olarak işlediği veri genişliğidir; genel amaçlı
yazmaçların genişliğiyle örtüşür. 64 bitlik bir işlemcide sözcük sekiz bayttır. Terimin
mimariye özgü kullanımı bundan ayrılabilir: x86 el kitabı, işlemci 64 bitlik olsa bile
`word`'ü iki bayt sayar ve sekiz baytlık birimi `quadword` diye anar.

Donanım, çok baytlı erişimleri sözcük sınırlarına göre yapar. Bu nedenle veri
yapılarının bellekte yerleşimi **hizalama (alignment)** kuralına uyar: $n$ baytlık bir
değerin adresi, genellikle $n$'in katı olacak biçimde seçilir. Dört baytlık bir tam
sayı `1000` adresine yerleşir; `1001` adresine değil.

Hizalanmamış erişimin bedeli donanıma göre değişir: kimi işlemciler böyle bir erişimi
iki bellek işlemine bölerek yavaşlatır, kimileri hata üretir.

Hizalama kuralının görünür sonucu **dolgudur (padding)**. Bir yapı içinde küçük ve
büyük alanlar ardışık tanımlandığında derleyici araya kullanılmayan baytlar ekler:

```
struct A            struct B
  1 baytlık alan      8 baytlık alan
  7 bayt dolgu        1 baytlık alan
  8 baytlık alan      7 bayt dolgu
  toplam: 16 bayt     toplam: 16 bayt
```

Aynı alanları farklı sırayla tanımlamak, yapının bellekteki boyutunu değiştirebilir.
Alanları genişliğe göre azalan sırada dizmek, dolguyu en aza indiren yaygın bir
alışkanlıktır. Bu ayrıntı, bir önceki derste ham bellek kopyasının neden taşınabilir
olmadığını da açıklar: dolgu miktarı derleyiciye ve donanıma bağlıdır.

#### Bellek Yerleşimini Görmek

Aşağıdaki program, aynı verinin farklı gösterimlerini ve bir nesnenin bellekte
kapladığı yeri inceler:

```python
import sys, array

deger = 0x41424344

# Belleğe yazılacak dört bayt (büyük uçlu düzende)
ham = deger.to_bytes(4, "big")
for ofset, bayt in enumerate(ham):
    print(f"adres+{ofset}: {bayt:#04x}")     # 0x41 0x42 0x43 0x44

# Sabit genişlikli diziler, elemanları bitişik ve hizalı tutar.
dizi = array.array("I", [1, 2, 3, 4])        # 'I': 4 baytlık işaretsiz tam sayı
print(dizi.itemsize, len(dizi))              # 4 4
print(dizi.buffer_info()[1] * dizi.itemsize) # 16  — toplam bayt

# Python nesneleri ham veriden çok daha fazla yer tutar.
print(sys.getsizeof(1), sys.getsizeof(dizi)) # nesne başlığı dahil boyutlar
```

`array` modülü, elemanları sabit genişlikte ve bitişik saklar; bu, veri yapılarının
donanım dostu yerleşimine en yakın Python karşılığıdır. Buna karşılık genel amaçlı bir
liste, elemanlarına işaretçiler tutar: değerlerin kendisi bellekte dağınık durur. İki
yerleşim arasındaki fark, sonraki derste ele alınacak önbellek davranışında görünür
hâle gelir.

#### Özet

- Bellek, programın gözünde adreslenmiş bayt hücrelerinden oluşan bir dizidir; erişim
  hem adres hem genişlik gerektirir.
- Adres de bir sayıdır ve bellekte saklanabilir; işaretçi kavramı bu gözlemden çıkar.
- İşlemci yalnızca yazmaçlar üzerinde işlem yapar; hesap, yükle–işle–sakla düzeninde
  yürür.
- Yazmaç, önbellek, ana bellek ve kalıcı depolama arasında mertebe düzeyinde hız farkı
  vardır; bu kademelenme bellek hiyerarşisidir.
- Çok baytlı değerler hizalama kuralına göre yerleşir; kural, yapılarda dolgu baytlarına
  ve alan sırasına bağlı boyut farklarına yol açar.

#### Sonraki Adım

Verinin nerede durduğu tanımlandı. Komutların kendisi de bellekte durur ve aynı adres
uzayını paylaşır. Sonraki ders, işlemcinin bir komutu bellekten alıp yürütme döngüsünü
ve bu döngünün program akışını nasıl oluşturduğunu ele alacak; orada `0x41424344`
örüntüsü, bu kez bir komut olarak okunacak.

### Komutlar ve Program Sayacı

Source: https://academia.sh/tr/kurslar/bilgisayarlar-nasil-calisir/komutlar-ve-program-sayaci

Komutların bit örüntüsü olarak kodlanması, getir–çöz–yürüt döngüsü ve program akışının oluşumu.

Önceki ders, verinin bellekte adreslenmiş baytlar olarak durduğunu kurdu. Komutlar da
aynı bellekte, aynı biçimde durur: bir komut, belirli bir kurala göre kodlanmış bit
örüntüsünden ibarettir. Program ile veri arasındaki ayrım donanımda değil, o baytlara
uygulanan yorumdadır.

Bu dersin sorusu şudur: işlemci, bellekteki bir bit örüntüsünü nasıl bir eyleme çevirir
ve bir sonraki eylemi nasıl belirler?

#### Komut Kümesi

Her işlemci ailesinin tanımlı bir **komut kümesi (instruction set)** vardır: hangi
işlemlerin var olduğunu, her birinin hangi bit örüntüsüyle kodlandığını ve hangi
işlenenleri aldığını belirten sözleşme. Komut kümesi, donanım ile yazılım arasındaki
arayüzdür; aynı komut kümesini uygulayan iki farklı işlemci, aynı programı çalıştırabilir.

Tipik komut sınıfları azdır:

- **Veri taşıma** — bellekten yazmaca yükleme, yazmaçtan belleğe saklama.
- **Aritmetik ve mantık** — toplama, çıkarma, karşılaştırma, bit işlemleri.
- **Akış denetimi** — dallanma, alt yordam çağrısı ve dönüş.
- **Sistem** — kesme, ayrıcalık düzeyi değiştirme, işletim sistemi çağrısı.

Bir programlama dilindeki her yapı, sonunda bu sınıflardan komutlara indirgenir.

#### Bir Komut Nasıl Kodlanır

Komut kodlaması, bit alanlarına bölünmüş bir örüntüdür. Alanlardan biri **işlem kodu
(opcode)**, hangi işlemin yapılacağını söyler; diğerleri işlenenleri belirtir.

Yapıyı somutlaştırmak için, yalnızca öğretim amacıyla tanımlanmış 32 bitlik bir kodlama
düşünün. Bu kodlama gerçek bir işlemciye ait değildir; alan yerleşimi her mimaride
farklıdır.

| Bit aralığı | Alan | Genişlik |
|---|---|---|
| 31–24 | işlem kodu | 8 bit |
| 23–16 | hedef yazmaç | 8 bit |
| 15–8 | birinci kaynak | 8 bit |
| 7–0 | ikinci kaynak | 8 bit |

Kursun ortak örneği bu kurala göre çözülürse:

$$
\texttt{0x41424344} \rightarrow \underbrace{\texttt{0x41}}_{\text{işlem}}\ \underbrace{\texttt{0x42}}_{\text{hedef}}\ \underbrace{\texttt{0x43}}_{\text{kaynak 1}}\ \underbrace{\texttt{0x44}}_{\text{kaynak 2}}
$$

`0x41` işlem kodunun "topla" anlamına geldiği varsayılırsa, komut şunu söyler: 67 ve 68
numaralı yazmaçların içeriğini topla, sonucu 66 numaralı yazmaca yaz.

Aynı otuz iki bit, bu konuya kadar dört kez yorumlandı: tam sayı, kayan noktalı sayı,
metin ve şimdi komut. Örüntüyü hangi yorumun karşılayacağına, işlemcinin o baytları
nereden okuduğu karar verir — komut olarak mı, veri olarak mı.

Sabit uzunluklu kodlamalar (her komut aynı genişlikte) çözme aşamasını basitleştirir.
Değişken uzunluklu kodlamalar ise sık kullanılan komutları daha az bayta sığdırarak
program boyutunu küçültür. İki yaklaşım da yaygındır ve seçim, mimarinin tasarım
önceliklerini yansıtır.

#### Makine Dili ve Çevirici Dili

Bit örüntülerini elle yazmak hataya açıktır. **Çevirici dili (assembly)**, her komuta
okunabilir bir ad veren birebir gösterimdir:

```
    yukle   r1, [1000]     ; 1000 adresindeki değeri r1'e yükle
    yukle   r2, [1004]
    topla   r3, r1, r2     ; r3 = r1 + r2
    sakla   [1008], r3
```

Çevirici dili ile makine dili arasındaki ilişki neredeyse birebirdir: her satır bir
komuta çevrilir. Yüksek seviyeli dillerdeki bir satır ise onlarca komuta karşılık
gelebilir; bu çeviriyi derleyici yapar ve konunun son dersinde ele alınır.

#### Getir–Çöz–Yürüt Döngüsü

İşlemci, açıldığı andan kapanana kadar tek bir döngüyü tekrarlar:

1. **Getir (fetch).** Program sayacının gösterdiği adresten komut okunur.
2. **Çöz (decode).** Bit alanları ayrıştırılır; işlem kodu ve işlenenler belirlenir.
3. **Yürüt (execute).** İşlem, aritmetik ve mantık birimi veya bellek birimi tarafından
   gerçekleştirilir.
4. **Sonucu yaz.** Üretilen değer hedef yazmaca veya belleğe yazılır.
5. Program sayacı bir sonraki komuta ilerletilir ve döngü baştan başlar.

**Program sayacı (program counter)**, bir sonraki komutun adresini tutan özel bir
yazmaçtır. Sıradan komutlarda her adımda komut genişliği kadar artar; program bu yüzden
"yukarıdan aşağı" akar.

Bu döngü, işlemcinin yaptığı her şeyin tanımıdır. Bir işletim sistemi, bir tarayıcı ya
da bir oyun arasındaki fark, döngüde değil, döngünün okuduğu komut dizisindedir.

#### Adımların Örtüşmesi

Döngünün adımları farklı donanım birimlerini kullanır: getirme bellek arayüzünü, çözme
denetim birimini, yürütme aritmetik birimini meşgul eder. Her adımı sırayla beklemek, bu
birimlerin çoğunu her an boşta bırakır.

**Boru hattı (pipeline)** bu boşluğu kapatır: bir komut yürütülürken bir sonraki
çözülür, üçüncüsü getirilir. Tek bir komutun tamamlanma süresi değişmez; birim zamanda
tamamlanan komut sayısı artar.

Boru hattının bedeli dallanmadır. Koşullu dallanmada bir sonraki komutun adresi, koşul
hesaplanana kadar bilinmez; boru hattına hangi komutların alınacağı belirsizdir. İşlemci
bir tahminde bulunur ve tahmin yanlış çıkarsa hattaki komutları atar. Tahmin edilmesi güç
dallanmalar bu yüzden ölçülebilir bir maliyet taşır.

Bu, ilk konudaki bir gözlemi tekrarlar: komut sayısı, sürenin tek belirleyicisi
değildir.

#### Akışın Değiştirilmesi

Program sayacına doğrudan yazılabilmesi, tüm denetim yapılarının kaynağıdır.

**Koşulsuz dallanma**, program sayacına yeni bir adres yazar; akış oradan devam eder.
**Koşullu dallanma** ise önce bir karşılaştırma sonucuna bakar. Karşılaştırma komutları,
sonucu bayrak bitlerine yazar — sonuç sıfır mı, negatif mi, taşma oldu mu. Koşullu
dallanma bu bayrakları okur ve program sayacını yalnızca koşul sağlanıyorsa değiştirir.

Yüksek seviyeli yapıların karşılığı böylece kurulur:

| Yapı | Komut karşılığı |
|---|---|
| `if` | Karşılaştır, koşul sağlanmıyorsa bloğun sonuna dallan |
| `while` | Koşulu sınayan dallanma + gövde sonunda başa dönen dallanma |
| Fonksiyon çağrısı | Dönüş adresini sakla, hedefe dallan; dönüşte saklanan adrese dallan |

Fonksiyon çağrısında dönüş adresinin nereye saklandığı, çağrı yığıtının işlevidir; yığıt
düzeni konunun son dersinde ele alınacaktır.

Aşağıdaki program, getir–çöz–yürüt döngüsünü öğretim amaçlı kodlama üzerinde canlandırır:

```python
ISLEM_ADLARI = {0x41: "topla", 0x42: "cikar", 0xFF: "dur"}


def coz(komut: int) -> tuple[str, int, int, int]:
    """32 bitlik komutu alanlarına ayırır."""
    islem  = (komut >> 24) & 0xFF
    hedef  = (komut >> 16) & 0xFF
    kaynak1 = (komut >> 8) & 0xFF
    kaynak2 = komut & 0xFF
    return ISLEM_ADLARI.get(islem, "bilinmeyen"), hedef, kaynak1, kaynak2


def calistir(bellek: list[int], yazmac: dict[int, int]) -> None:
    ps = 0                                   # program sayacı
    while ps < len(bellek):
        islem, hedef, k1, k2 = coz(bellek[ps])
        if islem == "dur":
            break
        if islem == "topla":
            yazmac[hedef] = yazmac.get(k1, 0) + yazmac.get(k2, 0)
        elif islem == "cikar":
            yazmac[hedef] = yazmac.get(k1, 0) - yazmac.get(k2, 0)
        ps += 1                              # bir sonraki komuta ilerle


print(coz(0x41424344))       # ('topla', 66, 67, 68)

bellek = [0x41424344, 0x42024344, 0xFF000000]
yazmac = {67: 10, 68: 4}
calistir(bellek, yazmac)
print(yazmac[66], yazmac[2]) # 14 6
```

`ps += 1` satırı, program sayacının sıradan ilerlemesidir. Bir dallanma komutu
eklenseydi, yaptığı tek şey bu değişkene farklı bir değer yazmak olurdu.

#### Özet

- Komutlar da veriler gibi bellekte durur; ayrım, baytlara uygulanan yorumdadır.
- Komut kümesi, donanım ile yazılım arasındaki sözleşmedir ve az sayıda komut sınıfı
  içerir.
- Bir komut, işlem kodu ve işlenen alanlarına bölünmüş bir bit örüntüsüdür; alan
  yerleşimi mimariye özgüdür.
- İşlemci getir–çöz–yürüt döngüsünü sürekli tekrarlar; programlar arasındaki fark
  döngüde değil, okunan komut dizisindedir.
- Program sayacı bir sonraki komutun adresini tutar; koşullu ve koşulsuz dallanma bu
  yazmacı değiştirerek tüm denetim yapılarını üretir.

#### Sonraki Adım

Döngünün her turu belleğe en az bir erişim gerektirir ve önceki derste görüldüğü gibi
ana bellek, işlemciye göre yüzlerce kat yavaştır. Sonraki ders, bu farkı kapatan
önbellek katmanını ve önbelleğin verimli çalışması için programın uyması gereken erişim
düzenini ele alacak.

### İşlemci Önbelleği

Source: https://academia.sh/tr/kurslar/bilgisayarlar-nasil-calisir/islemci-onbellegi

Önbellek katmanları, yerellik ilkesi, önbellek satırı ve erişim düzeninin başarıma etkisi.

Önceki iki ders iki gerçeği yan yana koydu: işlemci yalnızca yazmaçlar üzerinde işlem
yapar ve ana bellek erişimi yüzlerce saat çevrimi sürebilir. Bu iki gerçek doğrudan
birleştirilseydi, işlemci zamanının neredeyse tamamını beklemekle geçirirdi.

Bu dersin konusu, aradaki farkı kapatan katmandır: **önbellek (cache)**. Önbellek
donanım tarafından yönetilir ve programa görünmez; ancak programın davranışı, önbelleğin
işe yarayıp yaramayacağını belirler. Bu yüzden görünmez bir katman olmasına karşın
öğrenilmesi gerekir.

#### Yerellik İlkesi

Önbelleğin çalışması tek bir gözleme dayanır: programların bellek erişimleri rastgele
değildir. İki eğilim gözlenir.

**Zamansal yerellik (temporal locality):** Kısa süre önce erişilen bir adrese yeniden
erişilme olasılığı yüksektir. Bir döngü sayacı, döngü boyunca her turda
okunur ve yazılır.

**Uzamsal yerellik (spatial locality):** Bir adrese erişildiyse, komşu adreslere de
erişilme olasılığı yüksektir. Bir diziyi baştan sona gezen döngü, ardışık adresleri
sırayla okur; komutlar da bellekte ardışık durduğundan komut akışı aynı eğilimi gösterir.

Önbellek, bu iki eğilimden yararlanır: kısa süre önce kullanılan veriyi saklar (zamansal)
ve bir veri istendiğinde komşularını da getirir (uzamsal).

#### Önbellek Satırı

Önbellek ile ana bellek arasındaki alışveriş tek bayt üzerinden yapılmaz. Transfer
birimi **önbellek satırıdır (cache line)**; genişliği donanıma göre değişir ve tipik
olarak onlarca bayttır.

Bunun doğrudan bir sonucu vardır: bir bayt istendiğinde, o baytın bulunduğu satırın
tamamı getirilir. Dört baytlık bir tam sayıyı okumak ile aynı satırda bulunan on altı
tam sayıyı okumak, bellek trafiği açısından aynı maliyettedir.

Erişim iki sonuçtan birini verir:

- **İsabet (hit):** İstenen veri önbellektedir; erişim hızlıdır.
- **Iska (miss):** Veri önbellekte değildir; bir alt katmandan satır getirilir ve
  işlemci bu süre boyunca bekler.

Önbellek doluyken yeni bir satır getirmek, var olan bir satırı çıkarmayı gerektirir.
Hangi satırın çıkarılacağına **tahliye politikası** karar verir; en yaygın yaklaşım, en
uzun süredir kullanılmayan satırı çıkarmaktır. Aynı ilke, önbellekleme kavramının
geçtiği her katmanda — veritabanı önbelleği, içerik dağıtım ağı, tarayıcı önbelleği —
tekrar karşınıza çıkar.

#### Katmanlar

Önbellek tek bir birim değildir; hızdan kapasiteye doğru kademelenmiş katmanlardan
oluşur. Çekirdeğe en yakın katman en küçük ve en hızlıdır; uzaklaştıkça kapasite büyür,
gecikme artar. Alt katmanlar genellikle çekirdekler arasında paylaşılır.

Bu kademelenme, önceki derste verilen bellek hiyerarşisinin ortasını doldurur. Sayısal
değerler donanıma göre değişir; değişmeyen ilke, her katmanın bir üsttekinin daha yavaş
ve daha büyük bir kopyası olmasıdır.

Ayrı bir ayrım daha vardır: komutlar ve veriler çoğu tasarımda çekirdeğe en yakın
düzeyde ayrı önbelleklerde tutulur. Komut akışı ile veri akışının erişim örüntüleri
farklıdır ve birbirlerinin satırlarını atmamaları istenir.

#### Erişim Düzeninin Sonucu

Yerellik ilkesi, aynı işi yapan iki programın başarımının neden farklı olabileceğini
açıklar. İki boyutlu bir tabloyu toplamayı düşünün. Tablo bellekte satır satır saklanır:
bir satırın elemanları ardışık adreslerdedir, alt satır ise bir satır uzunluğu ötededir.

**Satır boyunca gezinme**, ardışık adresleri okur. İlk erişim ıska verir, satırın
tamamını getirir; sonraki erişimler isabet eder.

**Sütun boyunca gezinme**, her adımda bir satır uzunluğu atlar. Getirilen önbellek
satırının yalnızca bir elemanı kullanılır, gerisi kullanılmadan tahliye edilebilir.

İki döngü aynı sayıda toplama yapar; fark, bellek trafiğindedir. Bu fark, süre ölçmeden
de hesaplanabilir: getirilen önbellek satırı sayısı sayılır.

```python
SATIR_BAYT = 64          # örnek bir önbellek satırı genişliği
ELEMAN_BAYT = 8          # bir tam sayının genişliği
N = 1024                 # N x N tablo

eleman_sayisi = SATIR_BAYT // ELEMAN_BAYT      # bir satıra sığan eleman: 8

# Satır boyunca: getirilen her satırın sekiz elemanı da kullanılır.
satir_boyunca = N * N // eleman_sayisi

# Sütun boyunca: ardışık iki erişim arası uzaklık N * ELEMAN_BAYT bayt.
# Tablo önbelleğe sığmıyorsa, getirilen satırın yalnızca bir elemanı kullanılır.
sutun_boyunca = N * N

print(satir_boyunca, sutun_boyunca)            # 131072 1048576
print(sutun_boyunca / satir_boyunca)           # 8.0
```

Aynı sayıda toplama için taşınan veri miktarı sekiz kat farklıdır ve bu çarpan, bir
önbellek satırına sığan eleman sayısıdır. Elemanlar daraldıkça çarpan büyür: dört
baytlık elemanlarda on altı, tek baytlık elemanlarda altmış dörttür.

Bu hesap, gerçek süreyi doğrudan vermez. Ölçülen fark; önden getirme (prefetching),
adres çevirisi ve bellek denetleyicisinin davranışıyla birlikte belirlenir. Yorumlanan
dillerde ise eleman başına düşen yorumlama maliyeti, farkın büyük bölümünü örter; bu
yüzden yerellik etkisi en açık biçimde, elemanları bitişik saklayan ve eleman başına
yorumlama maliyeti taşımayan dillerde ölçülür.

#### Yazma Politikaları

Buraya kadar okuma anlatıldı. Yazma, ek bir karar gerektirir: değiştirilen veri alt
katmana ne zaman aktarılacaktır?

**İçinden yazma (write-through)** politikasında her yazma, hem önbelleğe hem alt
katmana işlenir. Katmanlar her an tutarlıdır; bedeli, her yazmanın yavaş katmana
erişmesidir.

**Arkadan yazma (write-back)** politikasında yazma yalnızca önbelleğe yapılır; satır
**kirli** olarak işaretlenir ve alt katmana ancak tahliye edilirken yazılır. Aynı
satıra arka arkaya yapılan yazmalar tek bir aktarıma iner; buna karşılık, alt katman
bir süre eski değeri taşır.

Aynı iki seçenek, önbelleğin geçtiği her katmanda karşınıza çıkar; veritabanı ve
uygulama önbelleklerinde de aynı adlarla anılır.

Çok çekirdekli sistemlerde ek bir sorun doğar: aynı bellek satırının kopyası birden çok
çekirdeğin önbelleğinde bulunabilir. Bir çekirdek kopyasını değiştirdiğinde diğerlerinin
elindeki kopya geçersizleşir. Donanım, bu tutarlılığı çekirdekler arası bir protokolle
sürdürür. Görünen sonucu şudur: farklı çekirdeklerde çalışan iş parçacıklarının aynı
önbellek satırına sık yazması, mantıksal olarak paylaşılan bir veri olmasa bile
başarımı düşürür.

#### Programa Yansıyan Sonuçlar

Önbellek doğrudan denetlenemez, ancak veri yerleşimi ve erişim sırası seçilebilir.
Önbellek dostu kodun ortak nitelikleri şunlardır:

- **Bitişik yerleşim.** Elemanları bitişik saklayan diziler, her elemanı ayrı yerde
  duran ve işaretçilerle bağlanan yapılara göre daha az ıska üretir. Bu, veri yapıları
  kursunda bağlı liste ile dizi karşılaştırmasının başarım boyutudur.
- **Erişim sırası ile yerleşim sırasının uyumu.** Veri hangi sırayla okunacaksa, o
  sırayla yerleştirilir.
- **Sıcak veriyi küçük tutmak.** Döngünün her turunda dokunulan veri kümesi bir
  önbellek katmanına sığıyorsa, döngü boyunca yalnızca isabet alınır.
- **Gereksiz alanları taşımamak.** Yapı içinde kullanılmayan alanlar, satır başına
  düşen yararlı veri miktarını azaltır.

Bu ilkelerin hiçbiri erken eniyileme gerektirmez. Anlamlı olan sıra şudur: önce doğru
çalışan ve okunabilir kod, sonra ölçüm, sonra ölçümün gösterdiği yerde bu ilkelerin
uygulanması.

#### Özet

- Önbellek, işlemci ile ana bellek arasındaki hız farkını kapatan, donanım tarafından
  yönetilen bir ara katmandır.
- Çalışmasının dayanağı yerellik ilkesidir: zamansal yerellik kısa süre önce kullanılanın
  tekrar kullanılacağını, uzamsal yerellik komşu adreslere erişileceğini öngörür.
- Transfer birimi önbellek satırıdır; bir bayt istendiğinde satırın tamamı getirilir.
- İsabet hızlı, ıska pahalıdır; önbellek dolduğunda tahliye politikası hangi satırın
  çıkarılacağına karar verir.
- Aynı işi yapan iki döngü, erişim düzenlerine göre farklı sürelerde çalışır; ardışık
  erişim sıçramalı erişimden hızlıdır.
- Önbellek doğrudan denetlenemez; denetlenebilen, veri yerleşimi ve erişim sırasıdır.

#### Sonraki Adım

Buraya kadar işlemci, komutları kendi belirlediği sırayla yürüten kapalı bir sistem gibi
ele alındı. Oysa dış dünya — klavye, disk, ağ arayüzü, zamanlayıcı — işlemcinin
dikkatini talep eder. Sonraki ders, bu talebin nasıl iletildiğini ve akışın nasıl
kesildiğini ele alacak.

### Kesmeler

Source: https://academia.sh/tr/kurslar/bilgisayarlar-nasil-calisir/kesmeler

Dış olayların işlemciye bildirilmesi, bağlam saklama, kesme türleri ve önalımlı çok görevliliğin temeli.

Önceki dersler işlemciyi kapalı bir sistem olarak ele aldı: komutları getirir, çözer,
yürütür ve bir sonrakine geçer. Oysa bir bilgisayar dış dünyayla etkileşir. Klavyeye
basılır, diskten okuma tamamlanır, ağdan paket gelir. Bu olayların ne zaman
gerçekleşeceği önceden bilinemez.

Bu dersin sorusu şudur: kendi döngüsüyle meşgul olan bir işlemciye, dış dünyada bir şey
olduğu nasıl bildirilir?

#### Yoklama ve Sınırı

Akla ilk gelen çözüm **yoklamadır (polling)**: program, aygıtın durumunu düzenli
aralıklarla sorar.

```
    döngü:
        aygıt durumunu oku
        veri hazır değilse döngüye dön
        veriyi işle
```

Yoklamanın iki maliyeti vardır. Aygıt uzun süre hazır olmazsa, işlemci hiçbir iş
üretmeden döner; her sorgu bir bellek erişimi ve birkaç komut harcar. Öte yandan sorgu
aralığı seyreltilirse bu kez gecikme artar: olay gerçekleştikten sonra fark edilene
kadar geçen süre uzar.

Yoklama her koşulda kötü değildir. Olayın çok sık ve öngörülebilir aralıklarla geldiği
durumlarda, kesme işlemenin sabit maliyetinden ucuza gelebilir. Ancak genel çözüm
olamaz.

#### Kesme Mekanizması

**Kesme (interrupt)**, aygıtın işlemciye ilettiği bir sinyaldir. İşlemci bu sinyali
aldığında yürüttüğü komutu tamamlar ve akışı kendiliğinden başka bir yere aktarır.
Adımlar şunlardır:

1. Yürütülmekte olan komut tamamlanır.
2. **Bağlam saklanır:** program sayacı ve gerekli yazmaçlar, akış sonradan kaldığı
   yerden sürebilsin diye bir kenara yazılır.
3. Kesme numarası kullanılarak **kesme vektör tablosundan** ilgili işleyicinin adresi
   okunur.
4. Program sayacına bu adres yazılır; **kesme işleyicisi (interrupt handler)** çalışır.
5. İşleyici bittiğinde bağlam geri yüklenir ve kesilen program, kesildiğini fark
   etmeden devam eder.

Kesilen programın bakış açısından hiçbir şey olmamıştır: yazmaçlar aynı, program sayacı
beklenen yerdedir. Yalnızca geçen zaman farklıdır.

Bu mekanizmanın maliyeti sıfır değildir. Bağlam saklama ve geri yükleme birkaç komutluk
iş yapar ve önbellek içeriğini bozabilir. Kesme işleyicilerinin kısa tutulması bu
yüzden bir tasarım kuralıdır: işleyici yalnızca acil olanı yapar, uzun süren işi
sonradan çalışacak bir göreve bırakır.

#### Kesme Türleri

Akışı kesen olaylar tek türden değildir.

**Donanım kesmesi** eşzamansızdır: dış aygıttan gelir ve programın hangi komutta olduğu
ile ilgisi yoktur. Klavye, disk denetleyicisi, ağ arayüzü ve zamanlayıcı bu sınıftandır.

**İstisna (exception) veya tuzak (trap)** eşzamanlıdır: yürütülmekte olan komutun
kendisi tarafından üretilir. Sıfıra bölme, geçersiz komut, izinsiz bellek erişimi ve
sayfa hatası bu sınıftadır. Aynı programı aynı girdiyle yeniden çalıştırmak, istisnayı
aynı noktada üretir.

Ayrım pratiktir: eşzamansız bir kesme ertelenebilir, eşzamanlı bir istisna
ertelenemez — komut, istisna ele alınmadan tamamlanamaz.

**Yazılım kesmesi**, programın kendi isteğiyle ürettiği tuzaktır ve işletim sistemi
çağrılarının temelidir. Kullanıcı programı, ayrıcalık gerektiren bir iş için (dosya
açmak, ağ soketi oluşturmak) doğrudan donanıma erişemez; bunun yerine bir tuzak üretir,
ayrıcalıklı kod çalışır ve sonucu döndürür. Kullanıcı kipi ile çekirdek kipi arasındaki
sınır bu noktada çizilir.

#### Zamanlayıcı Kesmesi ve Önalım

Kesmelerin en önemlisi, dış dünyadan değil, işlemcinin kendi zamanlayıcısından gelir.
Bu zamanlayıcı düzenli aralıklarla kesme üretir.

Sonucu belirleyicidir: hiçbir program, işlemciyi süresiz elinde tutamaz. Zamanlayıcı
kesmesi geldiğinde denetim işletim sistemine geçer; işletim sistemi çalışan programın
bağlamını saklar, başka bir programın bağlamını yükler ve akış oradan devam eder. Bu
düzene **önalımlı (preemptive) çok görevlilik** denir.

Önalım olmasaydı, çok görevlilik programların gönüllü işbirliğine kalırdı; sonsuz döngüye
giren tek bir program tüm sistemi kilitlerdi. Süreç ve iş parçacığı planlaması, işletim
sistemi kavramları kursunda bu temel üzerine kurulur.

#### Doğrudan Bellek Erişimi

Kesme, aygıtın "hazırım" demesini ucuzlatır; ancak verinin taşınması hâlâ işlemcinin
işiyse kazanç sınırlı kalır. Diskten okunan bir bloğu bayt bayt yazmaçlara alıp belleğe
yazmak, işlemciyi taşıma işine bağlar.

**Doğrudan bellek erişimi (direct memory access)**, taşımayı ayrı bir denetleyiciye
devreder. İşlemci yalnızca isteği tanımlar: kaynak, hedef adres ve uzunluk. Denetleyici
aktarımı kendisi yürütür; işlemci bu sırada başka komutlar işler. Aktarım bittiğinde
denetleyici bir kesme üretir.

Bu düzen, kesmenin asıl kazancını ortaya çıkarır: işlemci ne veriyi bekler ne de
taşır; yalnızca başlatır ve bittiğinde haberdar edilir. Aynı ilke üst katmanlarda da
sürer — eşzamansız girdi/çıktı arayüzleri, "isteği ver, sonucu bildirilince al" düzenini
yazılım düzeyinde tekrarlar.

Bedeli, paylaşılan bellek üzerinde iki yazıcı bulunmasıdır: denetleyici belleği
değiştirirken işlemcinin önbelleğinde o bölgenin eski kopyası durabilir. Tutarlılığın
korunması donanım ve sürücü sorumluluğundadır.

#### Maskeleme ve Öncelik

Kesme işleyicisi çalışırken yeni bir kesme gelebilir. Donanım bu durumu iki araçla
yönetir.

**Maskeleme:** Belirli kesmeler geçici olarak bastırılabilir. Bölünmesi hâlinde tutarsız
duruma yol açacak kısa kod parçaları — kritik bölgeler — kesmeler kapalıyken çalışır.
Bu sürenin uzun tutulması, sistemin dış olaylara yanıt gecikmesini doğrudan artırır.

**Öncelik:** Kesmeler önem sırasına göre derecelendirilir; yüksek öncelikli bir kesme,
düşük öncelikli bir işleyiciyi kesebilir. İç içe geçen kesmeler, bağlamların yığıt
üzerinde birikmesi anlamına gelir.

Tüm kesmelerin bastırılamadığı bir sınıf da vardır: donanım arızası gibi ertelenmesi
anlamsız olaylar için ayrılmış maskelenemez kesmeler.

#### Programa Yansıması

Kesme donanım düzeyinde bir kavramdır, ancak izleri yazılımda görünür. Bir işletim
sistemi sinyali, süreç için kesmenin karşılığıdır: akış nerede olursa olsun, kayıtlı
işleyici çalışır ve sonra kaldığı yerden devam eder.

```python
import signal, time

kesme_sayisi = 0

def zamanlayici_isleyicisi(sinyal_no, cerceve):
    """Akış nerede olursa olsun bu işlev çalışır ve sonra geri dönülür."""
    global kesme_sayisi
    kesme_sayisi += 1

signal.signal(signal.SIGALRM, zamanlayici_isleyicisi)
signal.setitimer(signal.ITIMER_REAL, 0.05, 0.05)    # 50 ms'de bir sinyal

baslangic = time.perf_counter()
sayac = 0
while time.perf_counter() - baslangic < 0.5:        # yarım saniyelik yoğun döngü
    sayac += 1

signal.setitimer(signal.ITIMER_REAL, 0)             # zamanlayıcıyı durdur
print(kesme_sayisi)                                 # 9 dolayında
```

Döngüde sinyali bekleyen hiçbir satır yoktur; buna karşın işleyici yaklaşık dokuz kez
çalışmıştır. Kesme kavramının ayırt edici yanı budur: denetim, akışın kendisi
istemeden aktarılır.

Benzetmenin sınırı da belirtilmelidir. İşletim sistemi sinyalleri donanım kesmesi
değildir; çalışma zamanı bunları güvenli noktalarda işler. Yine de akışın dışarıdan
kesilmesi fikri aynıdır.

Aynı fikir daha üst katmanlarda da sürer: eşzamansız girdi/çıktı, olay döngüleri ve geri
çağrı tabanlı arayüzler, "hazır olduğunda haber ver" ilkesinin yazılım düzeyindeki
karşılıklarıdır.

#### Özet

- Yoklama, aygıt durumunu düzenli sorgular; işlemci zamanı harcar veya gecikme artar.
- Kesme, aygıtın işlemciye ilettiği sinyaldir; işlemci bağlamı saklar, işleyiciyi
  çalıştırır ve akışı kaldığı yerden sürdürür.
- Donanım kesmeleri eşzamansız, istisnalar eşzamanlıdır; yazılım kesmeleri ise işletim
  sistemi çağrılarının temelidir.
- Zamanlayıcı kesmesi, hiçbir programın işlemciyi süresiz tutamamasını sağlar ve
  önalımlı çok görevliliği mümkün kılar.
- Kritik bölgelerde kesmeler maskelenir; maskeleme süresi uzadıkça sistemin yanıt
  gecikmesi artar.
- Kesme işleyicileri kısa tutulur; bağlam saklama ve geri yükleme maliyetlidir.

#### Sonraki Adım

Bu konunun geri kalan sorusu, işlemin kendisidir: toplama komutu çalıştığında donanımda
ne olur? Sonraki ders, aritmetiğin mantık kapılarıyla nasıl kurulduğunu ve ikiye
tümleyen gösteriminin neden çıkarma için ayrı bir devre gerektirmediğini gösterecek.

### Aritmetiğin Donanımdaki Karşılığı

Source: https://academia.sh/tr/kurslar/bilgisayarlar-nasil-calisir/aritmetigin-donanimdaki-karsiligi

Mantık kapılarından toplayıcı devrelere, çıkarmanın aynı devrede yapılması ve durum bayraklarının üretimi.

İkinci derste, `topla` komutunun 66 numaralı yazmaca bir sonuç yazdığı söylendi ama bu
sonucun nasıl üretildiği açık bırakıldı. Bu ders o boşluğu doldurur: toplama işlemi,
donanımda hangi yapılarla gerçekleşir?

Yanıt, ilk konudaki iki dersi birleştirir. Bit düzeyi işleçler dersinde tanımlanan VE,
VEYA ve XOR işlemleri; işaretli tam sayılar dersinde tanımlanan ikiye tümleyen
gösterimi. İkisi bir araya geldiğinde, aritmetik birimin tamamı kurulur.

#### Mantık Kapıları

**Mantık kapısı (logic gate)**, bir veya iki giriş bitinden bir çıkış biti üreten
fiziksel devre öğesidir. Davranışları, önceki konuda tanımlanan doğruluk tablolarının
aynısıdır: VE kapısı yalnızca iki girişi de $1$ ise $1$ üretir, VEYA kapısı en az biri
$1$ ise, XOR kapısı girişler farklı ise.

Bu, o dersteki işleçlerin kaynağını da açıklar: `a & b` ifadesi, sözcük genişliği kadar
VE kapısının aynı anda çalışması demektir. Bit düzeyi işlemlerin ucuz olmasının nedeni
budur — her bit için tek bir kapı yeterlidir ve bitler birbirini beklemez.

Aritmetik ise bu bağımsızlığı bozar: bir basamağın sonucu, alt basamaklardan gelen elde
bitine bağlıdır.

#### Yarım Toplayıcı

İki biti toplamak, elle yapılan toplamanın aynısıdır. $1 + 1$ işlemi, o basamakta $0$
bırakır ve bir üst basamağa $1$ taşır.

| $a$ | $b$ | Toplam | Elde |
|---|---|---|---|
| 0 | 0 | 0 | 0 |
| 0 | 1 | 1 | 0 |
| 1 | 0 | 1 | 0 |
| 1 | 1 | 0 | 1 |

Toplam sütunu XOR tablosunun, elde sütunu VE tablosunun aynısıdır:

$$
\text{toplam} = a \oplus b, \qquad \text{elde} = a \wedge b
$$

Bu iki kapılık devreye **yarım toplayıcı (half adder)** denir. Adındaki eksiklik şudur:
alt basamaktan gelen eldeyi hesaba katmaz.

#### Tam Toplayıcı

Gerçek bir toplamada her basamağın üç girdisi vardır: iki bit ve alt basamaktan gelen
elde. Bu üç girdiyi işleyen devre **tam toplayıcıdır (full adder)**:

$$
\text{toplam} = a \oplus b \oplus c_{\text{giriş}}
$$

$$
c_{\text{çıkış}} = (a \wedge b) \vee \big(c_{\text{giriş}} \wedge (a \oplus b)\big)
$$

Elde ifadesi sözle okunabilir: iki bit de $1$ ise elde üretilir; ya da bitlerden tam
biri $1$ iken alt basamaktan elde geliyorsa yine üretilir.

#### Dalgalı Elde Toplayıcı

$n$ bitlik toplama, $n$ tam toplayıcının zincirlenmesiyle kurulur: her toplayıcının elde
çıkışı, bir üstteki toplayıcının elde girişine bağlanır. Bu düzene **dalgalı elde
toplayıcı (ripple-carry adder)** denir.

Zincirin bir maliyeti vardır: en üst basamağın sonucu, elde biti tüm basamaklardan
geçene kadar kararlı değildir. Devrenin gecikmesi bit sayısıyla orantılı büyür.

Bu gecikmeyi azaltmak, sayısal tasarımın klasik problemidir. Elde bitlerini basamak
basamak beklemek yerine, giriş bitlerinden doğrudan hesaplayan tasarımlar — elde
öngörülü toplayıcılar — daha fazla kapı kullanarak daha kısa gecikme sağlar. Alan ile
hız arasındaki bu değiş tokuş, donanım tasarımının her katmanında tekrarlanır.

#### Çıkarma İçin Ayrı Devre Yoktur

İşaretli tam sayılar dersinde, ikiye tümleyenin tercih edilme nedeninin toplama
devresini değiştirmemesi olduğu söylenmişti. Bunun devredeki karşılığı şudur:

$$
a - b = a + (\lnot b) + 1
$$

$\lnot b$ değeri, $b$'nin bitlerini XOR kapılarından geçirerek üretilir; sondaki $+1$
ise en alt toplayıcının elde girişine $1$ verilerek sağlanır. Böylece tek bir denetim
biti hem tersleme hem de elde girişini yönetir: $0$ ise devre toplama, $1$ ise çıkarma
yapar.

Karşılaştırma işlemleri de aynı devreyi kullanır. İki değerin karşılaştırılması, farkın
hesaplanıp sonucun bayraklarına bakılmasıdır; sonucun kendisi saklanmaz.

#### Durum Bayrakları

Aritmetik ve mantık birimi, sonucun yanı sıra sonucu niteleyen bitler üretir. Dört
bayrak yaygındır:

| Bayrak | Kurulma koşulu |
|---|---|
| Sıfır | Sonucun tüm bitleri $0$ |
| İşaret | Sonucun en soldaki biti $1$ |
| Elde | En üst basamaktan elde çıkışı $1$ (işaretsiz taşma) |
| Taşma | En üst basamağa giren elde ile çıkan elde farklı (işaretli taşma) |

Son satır, işaretli taşmanın donanımdaki tanımıdır. İki pozitif sayı toplanırken en üst
basamağa elde girip çıkmıyorsa, sonuç işaret bitine taşmış demektir — değer negatif
görünür. $127 + 1$ örneğinde olan tam olarak budur.

Elde ve taşma bayraklarının ayrı tutulması, aynı toplama devresinin hem işaretsiz hem
işaretli yorumu desteklemesini sağlar. Hangi bayrağın anlamlı olduğuna, değeri hangi
yorumla kullandığını bilen program karar verir. Koşullu dallanma komutları da bu
bayrakları okur.

#### Devreyi Programla Kurmak

Aşağıdaki program, tam toplayıcıyı ve sekiz bitlik dalgalı elde toplayıcıyı bit düzeyi
işleçlerle kurar:

```python
def tam_toplayici(a: int, b: int, elde_giris: int) -> tuple[int, int]:
    """Üç giriş bitinden toplam ve elde çıkışını üretir."""
    toplam = a ^ b ^ elde_giris
    elde_cikis = (a & b) | (elde_giris & (a ^ b))
    return toplam, elde_cikis


def topla(x: int, y: int, bit: int = 8, cikar: bool = False) -> dict:
    """Dalgalı elde toplayıcı; cikar=True ise x - y hesaplar."""
    elde = 1 if cikar else 0
    sonuc = 0
    for i in range(bit):
        a = (x >> i) & 1
        b = ((y >> i) & 1) ^ (1 if cikar else 0)   # çıkarmada b terslenir
        if i == bit - 1:
            elde_giris_ust = elde                  # en üst basamağa giren elde
        t, elde = tam_toplayici(a, b, elde)
        sonuc |= t << i
    return {
        "sonuc": sonuc,
        "sifir": sonuc == 0,
        "isaret": ((sonuc >> (bit - 1)) & 1) == 1,
        "elde": elde == 1,
        "tasma": elde_giris_ust != elde,
    }


print(topla(5, 3))                    # 8, taşma yok
print(topla(5, 3, cikar=True))        # 2, çıkarma aynı devrede
print(topla(127, 1)["tasma"])         # True  — işaretli taşma
print(topla(255, 1)["elde"])          # True  — işaretsiz taşma
print(topla(255, 1)["sonuc"])         # 0     — başa sardı
```

`cikar` bayrağının iki yerde birden kullanılması — hem `b` bitlerinin terslenmesinde hem
de başlangıç eldesinde — donanımdaki tek denetim bitinin karşılığıdır.

Çıktılar, ilk konudaki iddiaları doğrular: $255 + 1$ işaretsiz yorumda başa sarar,
$127 + 1$ işaretli yorumda taşma bayrağını kurar. O derste "donanım her iki taşmayı da
ayrı bayraklarla bildirir" denmişti; bu dersteki devre, o bayrakları üreten yapının
kendisidir.

#### Özet

- Mantık kapıları, bit düzeyi işleçlerin fiziksel karşılığıdır; her bit bağımsız
  işlendiği için bu işlemler ucuzdur.
- Yarım toplayıcı iki biti toplar: toplam XOR, elde VE kapısıyla üretilir.
- Tam toplayıcı alt basamaktan gelen eldeyi de hesaba katar; $n$ tam toplayıcının
  zincirlenmesi $n$ bitlik toplayıcıyı verir.
- Zincirleme gecikmesi bit sayısıyla artar; elde öngörülü tasarımlar daha çok kapı
  kullanarak gecikmeyi azaltır.
- Çıkarma ayrı bir devre gerektirmez: ikinci işlenen terslenir ve elde girişine $1$
  verilir.
- Aritmetik birim sonucun yanında sıfır, işaret, elde ve taşma bayraklarını üretir;
  elde işaretsiz, taşma işaretli yorumun taşmasını bildirir.

#### Sonraki Adım

Bu konu, verinin bellekte nasıl durduğunu ve komutların donanımda nasıl yürütüldüğünü
kurdu. Geriye tek bir halka kaldı: bir programcının yazdığı metin, bu komutlara nasıl
dönüşür? Sonraki konu, kaynak koddan çalışan sürece uzanan zinciri — derleme, bağlama ve
yükleme adımlarını — ele alacak.

## Kaynaktan Yürütülebilire

Kaynak metnin derlenmesi, bağlanması ve belleğe yüklenerek çalışan bir sürece dönüşmesi.

### Derleyici, Yorumlayıcı ve Sanal Makine

Source: https://academia.sh/tr/kurslar/bilgisayarlar-nasil-calisir/derleyici-yorumlayici-sanal-makine

Kaynak kodun yürütülmesine giden üç model, aralarındaki ödünleşimler ve dil ile gerçekleştirim ayrımı.

Önceki konu, işlemcinin bellekteki bit örüntülerini komut olarak yürüttüğünü kurdu.
Programcı ise bit örüntüsü yazmaz; okunabilir bir metin yazar. Aradaki mesafeyi bir
program kapatır.

Bu konunun sorusu şudur: `sonuc = a + b` satırı, işlemcinin yürütebileceği komutlara
nasıl dönüşür? Bu ders, dönüşümün ne zaman yapıldığına göre ayrışan üç modeli
tanımlar; sonraki dersler dönüşümün kendisini adım adım ele alacaktır.

#### Kaynak Kod Bir Metindir

Kaynak dosya, karakter kodlamaları dersinde tanımlanan kurallara göre yazılmış bir bayt
dizisidir. İçindeki `if`, `+` veya değişken adları, bilgisayar için henüz anlam taşımaz;
yalnızca karakterlerdir.

Bu metni yürütülebilir bir davranışa çevirmenin üç yolu vardır. Üçü de aynı işi yapar,
farkları **çevirinin ne zaman** yapıldığındadır.

#### Derleme

**Derleyici (compiler)**, kaynak metnin tamamını çalıştırmadan önce hedef makinenin
komutlarına çevirir. Çıktı, doğrudan yürütülebilen bir dosyadır.

Sonuçları:

- Çalışma anında çeviri maliyeti yoktur; program doğrudan işlemci hızında yürür.
- Derleyici tüm programı gördüğü için kapsamlı eniyileme yapabilir.
- Hataların bir bölümü çalıştırmadan önce, derleme sırasında yakalanır.
- Çıktı belirli bir komut kümesine ve işletim sistemine bağlıdır; başka bir platform
  için yeniden derleme gerekir.
- Kaynaktaki her değişiklik, çalıştırmadan önce bir derleme adımı gerektirir.

#### Yorumlama

**Yorumlayıcı (interpreter)**, kaynağı önceden çevirmez; yapıları sırayla okur ve
karşılık gelen işlemleri kendisi yürütür. Program çalışırken çeviri de sürer.

Sonuçları:

- Aynı kaynak, yorumlayıcısı bulunan her platformda çalışır; ayrı derleme gerekmez.
- Yazma ve çalıştırma arasındaki döngü kısadır.
- Her yapı, yürütüldüğü her seferde yeniden çözümlenir; bu, komut başına sabit bir
  maliyet ekler.
- Hataların çoğu, ilgili satır çalışana kadar görünmez.

Önbellek dersindeki hesabın neden zamanlama ölçümüyle gösterilmediği burada
açıklanır: yorumlama maliyeti eleman başına sabit bir yük eklediğinde, bellek
davranışından gelen fark bu yükün altında kalır.

#### Sanal Makine ve Bayt Kodu

Üçüncü model ikisini birleştirir. Kaynak, gerçek bir işlemcinin komut kümesine değil,
**sanal makine (virtual machine)** adı verilen tasarlanmış bir makinenin komut kümesine
çevrilir. Bu ara gösterime **bayt kodu (bytecode)** denir.

Bayt kodu, kaynak metinden kompakt ve çözümlenmesi ucuz; makine kodundan ise
platformdan bağımsızdır. Sanal makine, bayt kodunu yürüten programdır; her platform
için bir sanal makine yazmak, her platform için ayrı derleyici yazmaktan azdır.

Ara katmanın bedeli hızdır: bayt kodunu yürütmek, yerel komutları yürütmekten yavaştır.
**Anında derleme (just-in-time compilation)** bu bedeli azaltır. Sanal makine,
çalışırken sık yürütülen bölümleri saptar ve onları yerel makine koduna çevirir. Çeviri
maliyeti bir kez ödenir, kazanç her tekrarda alınır.

Anında derlemenin iki gözlenebilir sonucu vardır. Birincisi **ısınma**: programın ilk
turları sonrakilerden yavaştır, bu yüzden başarım ölçümleri ilk turlar atlanarak
yapılır. İkincisi, çalışma anındaki bilginin kullanılabilmesidir; derleyici, hangi
dalın gerçekte sık alındığını bilmeden tahmin ederken, anında derleyici gözlemleyerek
karar verebilir.

#### Dil ile Gerçekleştirimi Ayırmak

Yaygın bir karışıklık, bu modellerin dilin kendisine ait olduğunu sanmaktır. "Derlenen
dil" ve "yorumlanan dil" ifadeleri yanıltıcıdır.

Bir dil, sözdizimi ve anlamıyla tanımlanır; nasıl yürütüleceği **gerçekleştirimin**
kararıdır. Aynı dilin hem derleyicisi hem yorumlayıcısı yazılabilir ve pratikte
yazılmıştır. Yorumlanmakla bilinen dillerin derleyicileri, derlenmekle bilinen dillerin
yorumlayıcıları vardır.

Doğru cümle şudur: *bu dilin bu gerçekleştirimi, kaynağı bayt koda çevirip sanal
makinede yürütür.* Bu ayrım, bir dilin başarımı hakkındaki iddiaların hangi düzeye ait
olduğunu görmeyi sağlar.

#### Çeviriciyi Kim Çevirir

Derleyici de bir programdır ve o da bir dilde yazılmıştır. Bu, ilk bakışta döngüsel
görünen bir soru doğurur: bir dilin ilk derleyicisi hangi dille yazılır?

Yanıt basittir: başka bir dille. Yeni dilin ilk derleyicisi, var olan bir dilde yazılır
ve dilin küçük bir alt kümesini çevirebilir. Bu derleyici çalışır hâle geldiğinde, dilin
derleyicisi kendi diliyle yeniden yazılabilir ve ilk derleyiciyle derlenir. Bundan sonra
derleyici kendi kendini derler; buna **kendi kendini barındıran (self-hosting)**
derleyici denir.

Aynı akıl yürütme yorumlayıcılar ve sanal makineler için de geçerlidir. Zincirin en
altında her zaman, o platformda hâlihazırda çalışan bir program bulunur.

Bunun pratik sonucu, bir dilin yürütme modelinin dilin tanımına değil, ekosistemin
tarihine bağlı olmasıdır. Aynı dil için sonradan yazılan bir gerçekleştirim, bambaşka
bir model seçebilir.

#### Üç Modelin Karşılaştırması

| Ölçüt | Derleme | Yorumlama | Sanal makine |
|---|---|---|---|
| Çeviri zamanı | Çalıştırmadan önce | Çalışırken sürekli | Önce bayt koda, sonra çalışırken |
| Taşınabilirlik | Hedef platforma bağlı | Yorumlayıcı olan her yerde | Sanal makine olan her yerde |
| Yürütme hızı | En yüksek | En düşük | Arada; anında derleme ile artar |
| Geri bildirim döngüsü | Derleme adımı gerektirir | En kısa | Kısa |
| Hata yakalama | Derleme sırasında bir bölümü | Çalışma anında | İkisi arası |

Sınırlar keskin değildir. Derlenen bir dilin çalışma zamanı kütüphanesi olabilir;
yorumlanan bir dil, kaynağı önce ara gösterime çevirebilir. Modeller, gerçek
gerçekleştirimlerin üzerine oturduğu eksenlerdir.

#### İki Aşamayı Görmek

Python, bu üç modelin ikincisi ve üçüncüsü arasında durur: kaynak önce bayt koda
çevrilir, sonra sanal makinede yürütülür. İki aşama ayrı ayrı çağrılabilir:

```python
kaynak = "sonuc = a + b"

kod = compile(kaynak, "<ornek>", "exec")   # birinci aşama: metin -> kod nesnesi
print(type(kod))                            # <class 'code'>
print(kod.co_names)                         # ('a', 'b', 'sonuc')

ortam = {"a": 10, "b": 4}
exec(kod, ortam)                            # ikinci aşama: yürütme
print(ortam["sonuc"])                       # 14

import dis
dis.dis(kod)                                # üretilen bayt kodun listesi
```

`compile` çağrısı hiçbir şey yürütmez; yalnızca metni çözümleyip bir kod nesnesi
üretir. Yürütme, `exec` çağrısıyla ayrıca başlatılır. İki adımın ayrılabilmesi,
çevirinin yürütmeden farklı bir iş olduğunun doğrudan kanıtıdır.

`dis` çıktısındaki komut adları ve sayısı, gerçekleştirimin sürümüne göre değişir; bayt
kodu bir standart değil, sanal makinenin iç ayrıntısıdır. Kararlı olan, listenin
varlığıdır: kaynak metin, yürütülmeden önce bir komut dizisine dönüşmüştür.

#### Özet

- Kaynak kod bir metindir; yürütülebilmesi için makine komutlarına çevrilmesi gerekir.
- Derleyici çeviriyi çalıştırmadan önce yapar: hız ve derleme zamanı hata yakalama
  sağlar, çıktı platforma bağlıdır.
- Yorumlayıcı çeviriyi çalışırken yapar: taşınabilirlik ve kısa geri bildirim döngüsü
  sağlar, komut başına maliyet ekler.
- Sanal makine modeli, kaynağı platformdan bağımsız bayt koda çevirir; anında derleme
  sık yürütülen bölümleri yerel koda çevirerek aradaki farkı kapatır.
- Derlenme veya yorumlanma dilin değil, gerçekleştirimin özelliğidir.
- Bir dilin ilk çeviricisi başka bir dille yazılır; sonrasında kendi kendini derleyen
  bir çeviriciye geçilebilir.

#### Sonraki Adım

Bu ders çevirinin ne zaman yapıldığını ele aldı; nasıl yapıldığını değil. Sonraki ders,
kaynak metnin karakterlerden komutlara giden yolunu aşama aşama açacak: sözcüklere
ayırma, yapıyı çıkarma, anlam denetimi ve kod üretimi.

### Derleme Aşamaları

Source: https://academia.sh/tr/kurslar/bilgisayarlar-nasil-calisir/derleme-asamalari

Sözcüksel çözümleme, ayrıştırma, anlam denetimi, ara gösterim ve kod üretimi adımları.

Önceki ders, çevirinin ne zaman yapıldığına göre üç model tanımladı. Bu ders, çevirinin
kendisini açar: kaynak metin hangi adımlardan geçerek komutlara dönüşür?

Adımlar hemen her gerçekleştirimde aynıdır. Bir yorumlayıcı ile bir derleyici, ilk
aşamalarda aynı işi yapar; yollar sondaki üretim adımında ayrılır.

#### Sözcüksel Çözümleme

İlk aşama, karakter dizisini anlamlı en küçük birimlere ayırır. Bu birimlere **sözcük
birimi (token)** denir.

`toplam = 3 + 4 * 2` satırı şu birimlere ayrılır:

| Birim | Tür |
|---|---|
| `toplam` | ad |
| `=` | atama işleci |
| `3` | sayı sabiti |
| `+` | toplama işleci |
| `4` | sayı sabiti |
| `*` | çarpma işleci |
| `2` | sayı sabiti |

Bu aşamada boşluklar ve yorum satırları atılır; sayı sabitleri, karakter dizisinden
sayısal değere çevrilir. Sözcüksel çözümleyici yapıyla ilgilenmez: `3 + * 4` dizisi de
sorunsuz biçimde birimlere ayrılır, çünkü her birim tek başına geçerlidir.

Bu aşamada üretilen hatalar, dilin alfabesine ait olmayan karakterler veya
kapatılmamış dizgi sabitleri gibi durumlardır.

#### Ayrıştırma

İkinci aşama, sözcük birimlerinin dilin dilbilgisine uyup uymadığını denetler ve
uyuyorsa yapıyı ağaç biçiminde kurar. Bu ağaca **soyut sözdizim ağacı (abstract syntax
tree)** denir.

`3 + 4 * 2` ifadesinin ağacı, işleç önceliğini yapısına gömer:

```
      +
     / \
    3   *
       / \
      4   2
```

Çarpmanın toplamadan önce yapılması, ağaçta çarpma düğümünün daha derinde olmasıyla
kodlanmıştır: bir düğümün değeri hesaplanmadan üstündeki düğüm hesaplanamaz. Öncelik
kuralları, dilbilgisinin yazılış biçimiyle sağlanır; ayrı bir "öncelik denetimi" adımı
yoktur.

Ayrıştırma aşamasının hataları, birimlerin sırasına ilişkindir: kapatılmamış parantez,
eksik işlenen, beklenmeyen anahtar sözcük.

#### Anlam Denetimi

Sözdizimi doğru olan her program anlamlı değildir. `toplam = tanimsiz_ad + 1` satırı
dilbilgisine uyar, ancak adın neye karşılık geldiği bilinmiyorsa yürütülemez.

Bu aşamada derleyici bir **simge tablosu** kurar: her adın nerede tanımlandığını, türünü
ve kapsamını tutar. Ağaç bu tabloya karşı denetlenir:

- Kullanılan adlar tanımlı mı?
- İşleçlerin işlenen türleri uyumlu mu?
- Atamalarda tür dönüşümü geçerli mi?
- Fonksiyon çağrılarında argüman sayısı ve türleri doğru mu?

Statik tipli dillerde bu denetimlerin çoğu derleme sırasında yapılır. Dinamik tipli
dillerde tür bilgisi çalışma anında değerlerin üzerinde taşınır; aynı hatalar, ilgili
satır yürütüldüğünde ortaya çıkar. İki yaklaşım arasındaki fark, hatanın **ne zaman**
görüldüğüdür.

#### Ara Gösterim ve Eniyileme

Ağaç doğrudan makine koduna çevrilebilir, ancak çoğu derleyici araya bir katman koyar:
belirli bir işlemciye bağlı olmayan **ara gösterim (intermediate representation)**.

Ara gösterimin iki yararı vardır. Eniyilemeler burada, hedef makineden bağımsız olarak
yazılır; ve $m$ dil ile $n$ hedef mimari için $m \times n$ değil, $m + n$ bileşen yazmak
yeterli olur.

Bu düzeydeki tipik eniyilemeler:

- **Sabit katlama:** `3 + 4 * 2` ifadesi çalışma anında değil, derleme sırasında
  hesaplanır ve yerine `11` konur.
- **Ölü kod eleme:** Sonucu hiçbir yerde kullanılmayan hesaplar çıkarılır.
- **Ortak alt ifade eleme:** Aynı hesabın tekrarı bir kez yapılıp saklanır.
- **Döngü değişmezini dışarı alma:** Döngü boyunca değişmeyen hesap, döngü öncesine
  taşınır.

Eniyilemenin sınırı, programın **gözlenebilir davranışını** değiştirmemektir. Bu
kısıt, kayan noktalı aritmetiğin neden serbestçe yeniden sıralanamayacağını açıklar:
toplama birleşme özelliği taşımadığından, sıra değişimi sonucu değiştirebilir.

#### Kod Üretimi

Son aşama, ara gösterimi hedef komut kümesine çevirir. Burada mimariye özgü kararlar
verilir:

- **Komut seçimi:** Aynı işlem birden çok komut dizisiyle yapılabilir; en ucuzu seçilir.
- **Yazmaç ayırma:** Değerlerin hangisi yazmaçta tutulacak, hangisi belleğe taşınacak.
  Yazmaç sayısı sınırlı olduğundan bu, derleyicinin en zorlu görevlerinden biridir.
- **Komut sıralama:** Boru hattının boşta kalmaması için komutlar yeniden sıralanabilir.

Bu aşamada uyulması gereken bir dış kural daha vardır: **çağrı sözleşmesi (calling
convention)**. Sözleşme, argümanların hangi yazmaçlara veya yığıt konumlarına
yerleştirileceğini, dönüş değerinin nerede taşınacağını ve hangi yazmaçları çağrılan
tarafın koruyacağını belirler. Ayrı ayrı derlenmiş dosyaların birbirini çağırabilmesi,
hepsinin aynı sözleşmeye uymasına bağlıdır; sözleşme derleyicinin değil, hedef
platformun tanımıdır.

Yorumlayıcılar bu aşamayı atlar: ağacı veya bayt kodu doğrudan yürütür. Sanal makine
modelinde ise hedef, gerçek bir işlemci değil sanal makinenin komut kümesidir.

#### Hata Bildirimi ve Kurtarma

Bir derleyici, ilk hatada durup çıkabilir. Bu davranış geliştirici için verimsizdir:
her hata ayrı bir derleme turu gerektirir. Bu nedenle ayrıştırıcılar **hata kurtarma**
uygular — hatayı bildirir, tutarlı bir noktaya kadar birimleri atar ve çözümlemeyi
sürdürür.

Kurtarmanın bedeli, ardışık hatalardır. Kapatılmamış tek bir parantez, sonraki
yapıların yanlış konumda görünmesine ve onlarca ek hata iletisine yol açabilir. Pratik
kural şudur: iletilerin ilki, en güvenilir olanıdır; sonrakiler ilkinin türevi olabilir.

Hata iletisinin niteliği de bir tasarım işidir. İyi bir ileti üç şeyi söyler: sorunun
ne olduğunu, nerede oluştuğunu ve ne beklendiğini. Bunun için ayrıştırıcı, ürettiği her
düğümde kaynak konumunu (satır ve sütun) taşır; bu bilgi ağaç boyunca korunur ve anlam
denetimi aşamasında da kullanılır.

#### Aşamaları Programla Görmek

Aşağıdaki program, ilk iki aşamayı küçük bir ifade dili üzerinde gerçekler ve üçüncü
aşamayı standart kütüphaneyle gösterir:

```python
import re, ast

DESEN = re.compile(r"\s*(?:(\d+)|([A-Za-z_]\w*)|(.))")

def sozcuklere_ayir(metin: str) -> list[tuple[str, str]]:
    """Kaynak metni (tür, değer) çiftlerine ayırır."""
    birimler = []
    for sayi, ad, simge in DESEN.findall(metin):
        if sayi:
            birimler.append(("sayi", sayi))
        elif ad:
            birimler.append(("ad", ad))
        elif simge.strip():
            birimler.append(("simge", simge))
    return birimler


print(sozcuklere_ayir("toplam = 3 + 4 * 2"))
# [('ad', 'toplam'), ('simge', '='), ('sayi', '3'), ('simge', '+'),
#  ('sayi', '4'), ('simge', '*'), ('sayi', '2')]

agac = ast.parse("3 + 4 * 2", mode="eval")
print(ast.dump(agac.body, annotate_fields=False))
# BinOp(Constant(3), Add(), BinOp(Constant(4), Mult(), Constant(2)))
```

`ast.dump` çıktısı, ayrıştırma bölümündeki ağacın metin biçimidir: dıştaki düğüm
toplama, sağ işleneni ise çarpma düğümüdür. Öncelik, ağacın biçimine yerleşmiştir.

Aynı programda sözcüksel çözümleyicinin `3 + * 4` girdisini sorunsuz ayırdığı, ancak
`ast.parse` çağrısının aynı girdide sözdizim hatası verdiği denenebilir; bu, iki
aşamanın farklı sorular sorduğunun doğrudan gösterimidir.

#### Özet

- Sözcüksel çözümleme, karakter dizisini sözcük birimlerine ayırır ve boşlukları atar.
- Ayrıştırma, birimlerin dilbilgisine uygunluğunu denetler ve soyut sözdizim ağacını
  kurar; işleç önceliği ağacın biçiminde kodlanır.
- Anlam denetimi, simge tablosuna karşı ad ve tür uyumunu sınar; statik ve dinamik tipli
  diller bu denetimi farklı zamanlarda yapar.
- Ara gösterim, eniyilemeleri hedef mimariden bağımsız kılar ve dil–mimari
  bileşimlerinin sayısını azaltır.
- Kod üretimi komut seçimi, yazmaç ayırma ve komut sıralama kararlarını verir.
- Bir derleme hatası, üretildiği aşama bilinerek daha çabuk okunur.

#### Sonraki Adım

Kod üretimi, her kaynak dosya için ayrı bir çıktı verir. Ancak bir program tek dosyadan
oluşmaz ve kullandığı kütüphaneler başka yerlerde derlenmiştir. Sonraki ders, bu
parçaların tek bir çalıştırılabilir bütüne nasıl bağlandığını ve çalışma anında belleğe
nasıl yüklendiğini ele alacak.

### Bağlama ve Yükleme

Source: https://academia.sh/tr/kurslar/bilgisayarlar-nasil-calisir/baglama-ve-yukleme

Nesne dosyaları, simge çözümleme, statik ve dinamik bağlama ile programın belleğe yüklenmesi.

Önceki ders, kod üretiminin her kaynak dosya için ayrı bir çıktı verdiğini söyledi.
Gerçek bir program ise birçok dosyadan oluşur ve kullandığı kütüphaneler başka
zamanlarda, başka yerlerde derlenmiştir.

Bu dersin sorusu şudur: ayrı ayrı derlenmiş parçalar, tek bir çalışan program hâline
nasıl getirilir?

#### Nesne Dosyası

Derleyicinin bir kaynak dosya için ürettiği çıktı doğrudan çalıştırılamaz; buna **nesne
dosyası (object file)** denir. İçinde makine kodu vardır, ancak eksik bilgiler taşır.

Nesne dosyası bölümlere ayrılmıştır. Adlandırma biçimlere göre değişse de içerik
ortaktır:

| Bölüm | İçerik |
|---|---|
| Kod | Yürütülebilir komutlar |
| Salt okunur veri | Sabitler, dizgi sabitleri |
| Başlatılmış veri | Başlangıç değeri verilmiş küresel değişkenler |
| Başlatılmamış veri | Yalnızca boyutu kayıtlı, sıfırlanacak alan |
| Simge tablosu | Tanımlanan ve gereken adlar |
| Yeniden konumlandırma kayıtları | Adres düzeltmesi gereken yerler |

Başlatılmamış veri bölümünün yalnızca boyutu saklanır; içeriği dosyada yer kaplamaz,
çünkü tamamı sıfırdır. Bu, büyük dizilerin tanımlandığı dosyaların neden küçük
kalabildiğini açıklar.

#### Simgeler

**Simge (symbol)**, bir ada bağlı bir adrestir: bir fonksiyonun başlangıcı veya bir
küresel değişkenin yeri. Nesne dosyası iki tür simge listeler:

- **Tanımlanan simgeler:** Bu dosyanın sağladığı adlar.
- **Gereken simgeler:** Bu dosyanın kullandığı, ancak başka bir yerde tanımlı adlar.

Derleyici, gereken bir simgeye yapılan çağrıyı üretirken hedef adresi bilmez; yerine
boşluk bırakır ve bu boşluğu yeniden konumlandırma kayıtlarına yazar. "Bu adresi, `topla`
adlı simgenin gerçek adresiyle doldur" notu, kayıtta durur.

#### Bağlama

**Bağlayıcı (linker)**, nesne dosyalarını ve kütüphaneleri alıp tek bir çıktı üretir.
Üç iş yapar:

1. **Bölümleri birleştirir.** Tüm dosyaların kod bölümleri art arda, veri bölümleri art
   arda yerleştirilir.
2. **Simgeleri çözer.** Her gereken simge için, onu tanımlayan dosya bulunur.
3. **Adresleri düzeltir.** Yeniden konumlandırma kayıtlarındaki boşluklar, birleştirme
   sonrası kesinleşen adreslerle doldurulur.

Bu aşamanın iki tanınmış hatası vardır ve ikisi de derleyici hatası değildir:

- **Tanımlanmamış simge:** Bir ad kullanılmış ama hiçbir dosyada tanımlanmamış. Yazım
  hatası, eksik kütüphane veya yalnızca bildirimi olup gövdesi yazılmamış bir fonksiyon
  bunu üretir.
- **Yinelenen simge:** Aynı ad birden çok dosyada tanımlanmış. Bağlayıcı hangisini
  seçeceğini bilemez.

Bu hataların derleme değil bağlama aşamasında çıkması, tek bir dosyanın sorunsuz
derlenmesinin programın bütününün doğru olduğu anlamına gelmediğini gösterir.

#### Görünürlük ve Ad Karıştırma

Bir dosyadaki her ad, bağlayıcıya görünmek zorunda değildir. Diller, simgeleri iki
görünürlük düzeyine ayırır:

- **Dış görünürlük:** Ad, simge tablosuna yazılır ve başka dosyalardan çözülebilir.
- **İç görünürlük:** Ad yalnızca kendi dosyasında geçerlidir; bağlayıcı onu görmez.

İç görünürlük iki işe yarar: aynı adın farklı dosyalarda çakışmadan kullanılabilmesi ve
bağlayıcının çözmesi gereken simge sayısının azalması. Büyük projelerde bu, bağlama
süresini doğrudan etkiler.

İkinci bir ayrıntı, aynı adın birden çok anlam taşıyabildiği dillerde ortaya çıkar. Aynı
ada sahip ama farklı parametre türleri alan iki fonksiyon, simge tablosunda aynı adla
duramaz. Derleyici bu durumda ada tür bilgisini kodlayarak benzersiz bir simge üretir;
buna **ad karıştırma (name mangling)** denir.

Ad karıştırma düzeni derleyiciye özgüdür ve standartlaşmış değildir. Farklı derleyicilerle
üretilmiş nesne dosyalarının birbirine bağlanamamasının başlıca nedeni budur; diller arası
çağrılarda ise karıştırmayı kapatan bir bildirim kullanılır.

#### Statik ve Dinamik Bağlama

Kütüphanelerin programa nasıl katıldığı iki ayrı yaklaşımla çözülür.

**Statik bağlamada** kütüphane kodu, çıktının içine kopyalanır. Sonuç tek ve büyük bir
dosyadır; çalıştığı sistemde başka bir şey aramaz. Kütüphanede sonradan yapılan bir
düzeltme, programın yeniden bağlanmasını gerektirir.

**Dinamik bağlamada** kütüphane ayrı bir dosya olarak kalır; çıktı yalnızca hangi
kütüphaneye ihtiyaç duyduğunu kaydeder. Bağlama işinin bir bölümü, programın
başlatıldığı ana ertelenir.

| Ölçüt | Statik | Dinamik |
|---|---|---|
| Çıktı boyutu | Büyük | Küçük |
| Bellek paylaşımı | Her süreç kendi kopyasını taşır | Aynı kütüphane süreçler arasında paylaşılır |
| Güncelleme | Yeniden bağlama gerekir | Kütüphane dosyası değişince tüm programlar etkilenir |
| Başlatma maliyeti | Yok | Kütüphane bulma ve simge çözme |
| Dağıtım | Kendi kendine yeter | Hedef sistemde uyumlu sürüm bulunmalıdır |

Dinamik bağlamanın güncelleme kolaylığı, aynı zamanda kırılganlık kaynağıdır: beklenenden
farklı bir sürümün bulunması, çalışma anında simge hatası verir. Uyumluluk kurallarının
belgelenmesi ve sürüm işaretlemesi bu nedenle kütüphane geliştirmenin ayrılmaz parçasıdır.

#### Yükleme

Program çalıştırıldığında **yükleyici (loader)** devreye girer. Yaptıkları:

1. Çalıştırılabilir dosyanın bölümlerini sürecin adres uzayına yerleştirir.
2. Başlatılmamış veri bölümü için yer ayırır ve sıfırlar.
3. Gerekli dinamik kütüphaneleri bulur, yükler ve kalan simgeleri çözer.
4. Yığıt için alan hazırlar, başlangıç argümanlarını yerleştirir.
5. Program sayacını giriş noktasına yazar; artık akış programındır.

Beşinci adım, önceki konudaki getir–çöz–yürüt döngüsünün başlangıcıdır. Yükleyici,
o döngünün ilk adresini belirleyen bileşendir.

Yükleyici, kodu önceden bilinmeyen bir adrese yerleştirebilir; bu nedenle üretilen
kodun konumdan bağımsız olması istenir. Konumdan bağımsız kod, mutlak
adres yerine göreli adres kullanır ve aynı kütüphanenin farklı süreçlerde farklı
adreslere yüklenmesine izin verir.

#### Çalışma Anında Ad Çözme

Dinamik bağlamanın fikri — adın hedefini son ana kadar belirlememek — yorumlanan
dillerde daha da ileri götürülür: ad çözümleme her erişimde yapılır.

```python
import math, sys

# İçe aktarma, adı çalışma anında bir modül nesnesine bağlar.
print(type(math))                      # <class 'module'>
print(math.__name__)                   # math

# Modül nesnesi, adları taşıyan bir sözlüktür.
print("sqrt" in vars(math))            # True

# Ad, çağrı anında çözülür: bağ değiştirilebilir.
gercek_sqrt = math.sqrt
math.sqrt = lambda x: "yerine geçen"
print(math.sqrt(9))                    # yerine geçen
math.sqrt = gercek_sqrt                # eski bağ geri yüklendi
print(math.sqrt(9))                    # 3.0

# Yüklü modüller, yükleyicinin tuttuğu tabloya benzer bir kayıtta durur.
print("math" in sys.modules)           # True
```

Bu esnekliğin bedeli, statik bağlamanın verdiği güvencelerin kaybıdır: yanlış yazılmış
bir ad, ilgili satır çalışana kadar hata üretmez. Bağlama zamanı ile hata görme zamanı
arasındaki ilişki, önceki derste tip denetimi için kurulan ilişkinin aynısıdır.

#### Özet

- Derleyicinin çıktısı olan nesne dosyası eksiktir: tanımladığı ve gereksinim duyduğu
  simgeleri listeler, dış adresler için boşluk bırakır.
- Bağlayıcı bölümleri birleştirir, simgeleri çözer ve adresleri düzeltir; tanımlanmamış
  ve yinelenen simge hataları bu aşamada çıkar.
- Statik bağlama kütüphane kodunu çıktıya kopyalar; dinamik bağlama onu ayrı dosyada
  bırakıp çözümü başlatma anına erteler.
- Dinamik bağlama bellek paylaşımı ve kolay güncelleme sağlar, karşılığında sürüm
  uyumluluğu sorumluluğu getirir.
- Yükleyici bölümleri adres uzayına yerleştirir, kütüphaneleri bağlar ve program
  sayacını giriş noktasına yazar.

#### Sonraki Adım

Yükleyicinin hazırladığı adres uzayının içi henüz açılmadı: kod nerede durur, küresel
değişkenler nerede, fonksiyon çağrılarının yerel değişkenleri nerede tutulur? Kursun son
dersi bu bölmeleri ve her birinin yaşam süresi kuralını ele alacak.

### Bellek Düzeni — Yığıt ve Öbek

Source: https://academia.sh/tr/kurslar/bilgisayarlar-nasil-calisir/bellek-duzeni

Bir sürecin adres uzayı bölmeleri, çağrı yığıtının işleyişi, öbek üzerinde ayırma ve yaşam süresi kuralları.

Yükleyici, çalıştırılabilir dosyanın bölümlerini sürecin adres uzayına yerleştirip
denetimi programa devretmişti. Bu ders, o adres uzayının içini açar: hangi veri nerede
durur ve ne kadar süre yaşar?

Soru pratiktir. Bir fonksiyonun yerel değişkeninin, fonksiyon döndükten sonra neden
geçersiz olduğu; sonsuz özyinelemenin neden çöküşle bittiği; ayrılan belleğin neden
serbest bırakılması gerektiği — hepsi bu düzenin sonuçlarıdır.

#### Adres Uzayının Bölmeleri

Bir sürecin adres uzayı, farklı davranışlara sahip bölmelere ayrılır:

```
  yüksek adresler
  ┌──────────────────────────┐
  │ Yığıt (stack)            │  ↓ aşağı doğru büyür
  ├──────────────────────────┤
  │            ...           │  boş alan
  ├──────────────────────────┤
  │ Öbek (heap)              │  ↑ yukarı doğru büyür
  ├──────────────────────────┤
  │ Başlatılmamış veri       │  sıfırlanmış küresel değişkenler
  ├──────────────────────────┤
  │ Başlatılmış veri         │  değer verilmiş küresel değişkenler
  ├──────────────────────────┤
  │ Salt okunur veri         │  sabitler
  ├──────────────────────────┤
  │ Kod                      │  komutlar; yazmaya kapalı
  └──────────────────────────┘
  düşük adresler
```

Yığıt ile öbeğin karşılıklı uçlardan büyümesi, aradaki boş alanı ikisinin ortak
kullanmasını sağlar: hangisi daha çok yer isterse ona doğru genişler.

Kod bölmesinin yazmaya kapalı olması bir güvenlik önlemidir: çalışan programın kendi
komutlarını değiştirmesi engellenir. Aynı biçimde, veri bölmelerinin çalıştırmaya
kapalı olması, veri olarak yerleştirilmiş baytların komut olarak yürütülmesini
engeller — bu ayrım, bellek güvenliği açıklarına karşı temel savunmalardan biridir.

#### Çağrı Yığıtı

Bir fonksiyon çağrıldığında, yığıtın tepesine o çağrıya ait bir **çerçeve (stack frame)**
eklenir. Çerçeve tipik olarak şunları taşır:

- **Dönüş adresi:** Fonksiyon bittiğinde akışın süreceği komutun adresi.
- **Argümanlar:** Yazmaçlara sığmayanlar.
- **Yerel değişkenler:** Fonksiyonun kendi değişkenleri.
- **Saklanan yazmaçlar:** Çağıran tarafın korunması gereken yazmaç içerikleri.

Bir yazmaç — yığıt işaretçisi — tepenin adresini tutar. Çağrıda tepe aşağı iner, dönüşte
yukarı çıkar. Kaydırılan tek şey bir yazmaç değeri olduğundan çerçeve ayırmak ve
bırakmak neredeyse ücretsizdir.

Bu düzenin yığıt yapısına uyması bir tesadüf değildir: çağrılar iç içedir. En son
çağrılan fonksiyon, en önce döner. Komutlar dersinde tanımlanan çağrı ve dönüş
komutlarının dönüş adresini nereye sakladığı sorusunun yanıtı budur.

Yerel değişkenlerin fonksiyon dönünce geçersizleşmesinin nedeni de görünür: çerçeve
bırakılmıştır ve o alan bir sonraki çağrıda kullanılacaktır. Yerel bir değişkenin adresini
döndürmek, kısa süre sonra başka bir çerçevenin işgal edeceği bir yeri göstermek demektir.

#### Yığıt Taşması

Yığıt sonsuz değildir; iş parçacığı başına sabit bir üst sınırla oluşturulur. Sınır iki
biçimde aşılır: çok derin çağrı zinciri veya çerçeve başına çok büyük yerel veri.

Taban durumu olmayan özyineleme, birinci biçimin kanonik örneğidir. Her çağrı bir çerçeve
ekler, hiçbiri dönmez ve yığıt tükenir.

```python
import sys

print(sys.getrecursionlimit())         # çalışma zamanının koyduğu sınır

def derinlik(n: int = 1) -> int:
    """Sınıra ulaşana kadar kendini çağırır ve ulaşılan derinliği döndürür."""
    try:
        return derinlik(n + 1)
    except RecursionError:
        return n

print(derinlik())                      # sınıra yakın bir sayı
```

Buradaki sınır donanımın yığıt sınırı değil, çalışma zamanının kendi koyduğu bir
korumadır: gerçek yığıt tükenip süreç çökmeden önce denetimli bir hata üretilir.
Koruması olmayan ortamlarda aynı program, işletim sisteminin sürece son vermesiyle
sonuçlanır.

Kuyruk özyinelemesi, programlama temelleri kursunda tanımlandığı biçimiyle, bu sorunun
bir çözümüdür: çağrı kuyruk konumundaysa yeni çerçeve açmak yerine var olan çerçeve
yeniden kullanılabilir. Bu dönüşümü her gerçekleştirim yapmaz.

#### İş Parçacığı Başına Yığıt

Bir süreçte birden çok iş parçacığı çalışıyorsa, her birinin kendi yığıtı olmak
zorundadır: çağrı zincirleri bağımsızdır ve bir iş parçacığının çerçeveleri diğerininkiyle
karışamaz.

Öbek ise paylaşılır. Bu asimetri, eşzamanlı programlamanın temel gerilimini kurar:
yığıttaki yerel değişkenler doğal olarak yalıtılmışken, öbekteki veriye birden çok iş
parçacığı aynı anda erişebilir. Paylaşılan durumun korunması gereği buradan doğar ve
eşzamanlılık, işletim sistemi kavramları kursunun konusudur.

Pratik bir sonuç da vardır: iş parçacığı başına ayrılan yığıt alanı, oluşturulabilecek
iş parçacığı sayısını sınırlar.

#### Öbek

Bazı veriler, onları yaratan fonksiyondan uzun yaşamak zorundadır; boyutu ise ancak
çalışma anında bilinir. Bunlar **öbek (heap)** üzerinde ayrılır.

Öbek yönetimi bir **ayırıcı (allocator)** tarafından yapılır. Ayırıcı, boş blokları
izler; istenen boyuta uygun bir blok bulur, işaretler ve adresini döndürür. Serbest
bırakılan blok yeniden kullanılabilir hâle gelir.

Bu esnekliğin üç maliyeti vardır:

- **Ayırma pahalıdır.** Uygun blok arama, yığıt işaretçisini kaydırmakla
  kıyaslanamayacak kadar çok iş içerir.
- **Parçalanma oluşur.** Ayır–bırak döngüleri, toplamı yeterli ama tek parça hâlinde
  yetersiz boşluklar bırakabilir.
- **Yaşam süresi elle yönetilir.** Serbest bırakılmayan blok **sızıntıdır**; serbest
  bırakıldıktan sonra kullanılan adres ise **sarkan işaretçidir** ve tanımsız davranış
  üretir.

Çöp toplayıcı kullanan dillerde üçüncü maliyet çalışma zamanına devredilir: erişilemez
hâle gelen nesneler otomatik olarak toplanır. Bunun bedeli, toplama sırasında oluşan
duraklamalar ve daha yüksek bellek kullanımıdır. Ödünleşim ortadan kalkmaz, yalnızca yer
değiştirir.

#### Yaşam Süreleri

Üç ayrı yaşam süresi kuralı, üç bölmeye karşılık gelir:

| Yaşam süresi | Yer | Başlangıç | Bitiş |
|---|---|---|---|
| Statik | Veri bölmeleri | Program yüklenirken | Program bitince |
| Otomatik | Yığıt | Çerçeve açılınca | Fonksiyon dönünce |
| Dinamik | Öbek | Ayırma isteğiyle | Serbest bırakma veya toplama ile |

Bir programın hataları çoğu zaman bu sütunların karıştırılmasından doğar: otomatik ömürlü
bir değere, ömrü bittikten sonra erişmek; dinamik ömürlü bir bloğu hiç bırakmamak; ya da
statik ömürlü paylaşılan bir değeri birden çok iş parçacığından eşzamanlı değiştirmek.

#### Kursun Toparlanması

Bu noktada zincirin tamamı kurulmuştur. Bir program metni sözcüklere ayrıldı, ağaca
dönüştü, ara gösterimden geçip makine komutlarına çevrildi; nesne dosyaları bağlandı,
yükleyici bölmeleri adres uzayına yerleştirdi ve program sayacını giriş noktasına yazdı.
İşlemci getir–çöz–yürüt döngüsünü çevirmeye başladı; komutlar yazmaçlar üzerinde
çalıştı, veriler önbellek katmanları üzerinden taşındı, dış olaylar kesmelerle bildirildi.
Ve tüm bu katmanların taşıdığı şey, ilk konunun konusu olan bit örüntüleriydi.

`0x41424344` örüntüsü kurs boyunca beş kez okundu: işaretsiz tam sayı, işaretli tam sayı,
kayan noktalı sayı, `ABCD` metni ve bir komut olarak. Kursun tek bir cümleye indirgenmiş
sonucu budur: **bellekte anlam yoktur; anlam, örüntüye uygulanan yorum kuralından
gelir.** Bir programlama dilinin tip sistemi, bir dosya biçiminin tanımı ve bir ağ
protokolünün belgesi, hep aynı işi yapar — hangi kuralın geçerli olduğunu söyler.

#### Özet

- Sürecin adres uzayı kod, salt okunur veri, başlatılmış ve başlatılmamış veri, öbek ve
  yığıt bölmelerine ayrılır; yığıt ile öbek karşılıklı uçlardan büyür.
- Her fonksiyon çağrısı yığıta bir çerçeve ekler; çerçeve dönüş adresini, argümanları ve
  yerel değişkenleri taşır.
- Çerçeve ayırmak bir yazmaç kaydırmasıdır; bu yüzden ucuzdur, ancak yığıt sınırlıdır ve
  derin çağrı zincirleri onu tüketir.
- Öbek, boyutu veya ömrü çalışma anında belirlenen veriler içindir; ayırma pahalıdır,
  parçalanma ve yaşam süresi yönetimi maliyet getirir.
- Statik, otomatik ve dinamik yaşam süreleri farklı bölmelere karşılık gelir; yaygın
  bellek hataları bu üçünün karıştırılmasından doğar.

#### Sonraki Adım

Bu kurs, bir programın altındaki katmanları — veri temsili, işlemci, bellek ve derleme
zinciri — kurdu. Bundan sonraki kurs, katmanın üstüne geçer: **Programlama Temelleri**,
değişken, denetim akışı, fonksiyon ve özyineleme kavramlarını dilden bağımsız olarak
ele alır. Bu kursta yalnızca örnek olarak kullanılan kod parçaları, orada dersin
konusunun kendisi olacaktır.

