Ders 11 / 13
Diziler ve Dizgiler
Bir dizinin eleman sayısının sabit ifade katlamasıyla derlemede, bir dizginin uzunluğunun sonlandırıcı bayta ulaşılana kadar koşumda belirlenmesi; dizi-işaretçi ilişkisinin adres basılmadan eleman değeri ve farkla ölçülmesi.
Yayın Güncelleme
Bu derste
- Dizi uzunluğunun derlemede, dizgi uzunluğunun koşumda belirlendiğini ölçerek ayırt eder
- Sonlandırıcı baytın dizginin sınırını koşumda nasıl belirlediğini örnekle gösterir
- Dizi ile işaretçi ilişkisini adres basmadan eleman değeri ve fark üzerinden ölçer
İçindekiler
Fonksiyonlar dersi, bir çağrının hangi tanıma bağlandığını, argümanın nasıl kopyalandığını ve dönüşün nasıl yorumlandığını ölçtü. Bu ders, aktarılan verinin kendisine dönüyor ve iki farklı veri türünün uzunluğunu nasıl taşıdığını karşılaştırıyor: bir dizinin uzunluğu ile bir dizginin uzunluğu, aynı bellek düzenine dayansa da, iki ayrı aşamada karara bağlanıyor.
Programlama Temelleri kursu (M01/K02) liste ve dizgi kavramlarını Python’la zaten kurmuştu;
orada bir listenin uzunluğu her zaman bir çağrıyla (len) sorulabilen, kendiliğinden
taşınan bir bilgiydi. C’nin karşılığı burada ölçülüyor: bir dizinin uzunluğu kaynakta
yazılan bir sabit ifadeden derlemede üretilirken, bir dizginin uzunluğu hiçbir yerde
saklanmaz — yalnız sonlandırıcı bir bayta ulaşılana kadar koşumda taranarak öğrenilir.
Bu ders, kursun ölçü ekseninin iki ucunu aynı anda gösteren tek ders: aynı bellek
düzeninde duran iki komşu değer, biri için “kaynak yeter”, öbürü için “yalnız koşum
yeter” yanıtını veriyor.
Sabit İfade Katlaması: Dizi Uzunluğu Derlemede
Aşağıdaki kesit, Derleme Aşamaları dersinde tanıtılan çekirdeğin bir parçasıdır: bir dizinin boyutu, kaynakta yazılan bir sabit ifadeden üretiliyor.
// asama.c — sabit ifade katlamasi (derleyici) ve dizgi uzunlugu (kosum) kesiti
#include <stdio.h>
#include <string.h>
int main(void) {
int sabit_dizi[2 * 3 + 1];
printf("%s %d\n", "sabit ifade katlamasi", (int)(sizeof sabit_dizi / sizeof sabit_dizi[0]));
const char *metin = "merhaba dunya";
printf("%s %d\n", "dizgi uzunlugu", (int)strlen(metin));
return 0;
}
sabit ifade katlamasi 7 dizgi uzunlugu 13
TS9. 2 * 3 + 1 ifadesi hiçbir değişkene bağlı değil — yalnız sabitlerden kurulu ve
derleyici bu ifadeyi program hiç çalışmadan katlayarak 7ye indirgiyor;
sabit_dizinin eleman sayısı, ikili dosyaya zaten 7 olarak yazılıyor. strlen(metin)
ise tam tersi: metin işaretçisinin gösterdiği bayt dizisi, \0 bulunana kadar bir bir
taranıyor ve uzunluk (13) ancak bu tarama bitince ortaya çıkıyor. İki ölçüm de aynı
satırda, aynı işlevde duruyor, ama biri kaynaktan okunabilecek bir sayıyı doğruluyor,
öbürü ancak çalıştırılarak öğrenilebilecek bir sayıyı üretiyor.
sabit_dizinin boyutu ikili dosyaya gömüldüğü için, sizeof sabit_dizi ifadesinin
kendisi de koşum sırasında hiçbir hesaplama gerektirmez — derleyici bu ifadeyi zaten bir
sabitle değiştirmiştir. Bu, sizeof işlecinin çoğu kullanımında derleme zamanı
bir işleç olduğunu gösteriyor: işlenenin gerçek değerine hiç bakmadan, yalnız tipine
bakarak bir sonuç üretiyor. sabit_dizi[2 * 3 + 1] gibi bir bildirimde parantez içindeki
ifade de aynı kategoriye giriyor — dizinin boyutunu belirleyen ifade, bir sabit
ifade olmak zorunda; içine bir değişken ya da bir işlev çağrısı girseydi, derleyici
boyutu artık önceden bilemez ve bu, C’nin ayrı bir konusu olan değişken uzunluklu
dizilere girerdi. Bu derste ölçülen dizi hep sabit uzunlukludur.
Sonlandırıcı Baytın Rolü: Dizgi Uzunluğu Koşumda
Bir dizginin uzunluğu hiçbir yerde ayrıca saklanmaz; strlen ve benzeri işlevler, bir
\0 baytına rastlayana kadar belleği tarayarak uzunluğu hesaplar. Sonlandırıcı
baytın konumu değiştiğinde, aynı bellek alanı farklı bir uzunluk raporlar.
// sonlandirici.c — sonlandirici baytin konumu, dizginin nerede bittigini kosumda belirler
#include <stdio.h>
#include <string.h>
int main(void) {
char kisa[6] = {'m', 'e', 'r', 'h', 'a', '\0'};
printf("%s %d\n", "sonlandirici sonda iken uzunluk", (int)strlen(kisa));
kisa[2] = '\0';
printf("%s %d\n", "sonlandirici erken konunca uzunluk", (int)strlen(kisa));
return 0;
}
sonlandirici sonda iken uzunluk 5 sonlandirici erken konunca uzunluk 2
TS10. kisa dizisi altı bayt ayırıyor ve ilk beşi harf, altıncısı \0; strlen
5 döndürüyor çünkü sonlandırıcıya beşinci bayttan sonra rastlıyor. kisa[2]
'\0'a değiştirildiğinde, dizinin kendisi hâlâ altı bayt — hiçbir bellek yeniden ayrılmadı,
hiçbir eleman silinmedi — ama strlen artık 2 döndürüyor, çünkü tarama üçüncü bayta
ulaşır ulaşmaz duruyor. Bu, bir dizginin “uzunluğu”nun aslında bir depolama özelliği
değil, bir koşum sonucu olduğunu gösteriyor: aynı bellek düzeni, sonlandırıcının
konumuna bağlı olarak farklı bir uzunluk raporlayabiliyor ve bu konum çalışma zamanında
değişebilen sıradan bir bayt.
Bu, C’de bir dizginin neden kendi başına bir tip olmadığını da açıklıyor: char kisa[6] bir char dizisidir, dilin gözünde altı ayrı char değerinden başka bir şey
taşımaz. “Dizgi”, yalnız bir kural — içindeki bir yerde bir \0 bayt bulunacağı ve
bu baytın sınırı işaretleyeceği kuralı — üzerine kurulu bir yorumdur, dilin kendisinin
denetlediği bir özellik değil. Bir tam sayı dizisi (int dizi[]) için böyle bir kural
yoktur — hangi elemanın “sonu” işaretlediğine dair bir sözleşme dilin bir parçası
değildir; bu yüzden bir tam sayı dizisinin uzunluğu ya kaynakta sabit bir ifadeyle
(yukarıdaki sabit_dizi gibi) belirlenir ya da ayrıca bir değişkende taşınır. Dizgi,
yalnız char dizileri için, sonlandırıcı bayt sözleşmesiyle, kendi uzunluğunu kendi
içinde taşıyan bir özel durumdur. kisa[2]ye elle '\0' yazmak, dizinin kendi bellek
düzenini hiç bozmuyor; yalnız bu kuralın hangi baytta durduğunu değiştiriyor. strlen
gibi işlevler bu kuralı varsayar — kendilerine geçirilen bir işaretçinin gösterdiği
bellekte bir yerde gerçekten bir \0 bulunacağını umarlar; sonlandırıcı hiç yoksa,
tarama dizinin ayrılmış sınırlarının ötesine taşar ve bu, kursun genel kuralınca
denenmeyen bir tanımsız davranış senaryosudur.
Dizi-İşaretçi İlişkisi: Adres Basmadan Ölçüm
Bir dizi adı, çoğu bağlamda ilk elemanının işaretçisine dönüşür (decay). Bu ilişki adres basılmadan, yalnız eleman değeri ve fark üzerinden ölçülebilir.
// iliski.c — dizi ile isaretci iliskisi, adres basmadan deger ve fark uzerinden olculur
#include <stdio.h>
int main(void) {
int dizi[] = {10, 20, 30, 40};
int *p = dizi;
printf("%s %d\n", "p artimliyla erisilen deger", *(p + 2));
printf("%s %d\n", "dizi indeksiyle erisilen deger", dizi[2]);
printf("%s %d\n", "iki erisim ayni mi", (int)(*(p + 2) == dizi[2]));
printf("%s %d\n", "adresler ayni mi", (int)((p + 2) == &dizi[2]));
printf("%s %d\n", "eleman cinsinden fark", (int)(&dizi[3] - &dizi[0]));
return 0;
}
p artimliyla erisilen deger 30 dizi indeksiyle erisilen deger 30 iki erisim ayni mi 1 adresler ayni mi 1 eleman cinsinden fark 3
TS11. p = dizi atamasıyla p, dizinin ilk elemanının adresini alıyor; *(p + 2)
ile dizi[2] aynı değeri (30) üretiyor, çünkü dizi[i] zaten *(dizi + i)nin kısa
yazımı — ikisi de aynı adresi hesaplayıp aynı değeri okuyor. (p + 2) == &dizi[2]
karşılaştırması da bunu doğruluyor: iki adres eşit. &dizi[3] - &dizi[0] farkı ise ham
bir bayt farkı değil, eleman cinsinden bir fark — sonuç 3, çünkü iki işaretçi
arasında üç int genişliğinde adım var; bu fark işlecinin C’de işaretçiler için özel
olarak tanımlanmış bir davranışı, int genişliğine bölünmüş hâliyle veriliyor. Hiçbir
adım gerçek bir adres yazdırmadı; ölçülen her şey bir değer, bir eşitlik ya da bir
eleman farkıydı.
dizi[2] yazımının *(dizi + 2)nin yalnız kısa bir yazımı olması, C’nin dizi
indekslemeye ayrı bir mekanizma ayırmadığını gösteriyor: köşeli parantez, işaretçi
aritmetiğinin üstüne kurulu bir sözdizimi kolaylığı. Bu, dizi[2] ile 2[dizi]
yazımlarının aynı anlama gelmesinin de nedeni — toplama işleci değişmeli olduğu için
dizi + 2 ile 2 + dizi aynı adresi verir ve iki yazım da aynı çeviriden geçer. Ders
bu eşdeğerliği ölçmüyor, ama dizi[i]nin altında yatan işlemin gerçekten bir toplama ve
bir yönlendirme (dereference) olduğunu — özel bir “dizi erişimi” komutu olmadığını —
doğruluyor. Bu, dil düzeyinde bir eşdeğerliktir: iki yazım da aynı adresi hesaplar ve
aynı değeri verir. Derleyicinin ürettiği makine kodunda kaç adım harcandığı ayrı bir
sorudur — dizi bir dizi adı, p ise bellekten okunması gereken bir değişkendir — ve
bu ders onu ölçmüyor.
Bu eşdeğerlik, dizinin kendisi ile dizinin dönüştüğü işaretçi arasında ince bir
fark bırakıyor: dizi adı kendi başına, dizinin tamamının adını taşırken, p bir
işaretçi değişkenidir ve başka bir diziyi göstermek üzere yeniden atanabilir — p = baska_dizi;
geçerlidir, ama dizi = baska_dizi; geçerli değildir, çünkü dizi bir dizi
bildirimidir, yeniden atanabilir bir işaretçi değişkeni değil. Dizi adı, bir ifadede
geçtiğinde kural olarak ilk elemanının adresine dönüşür; sizeof ile &
işleçlerinin işleneni olmak gibi sayılı durumlar bunun dışındadır. Dönüşen şey bir
adres değeridir, yeniden atanabilir bir işaretçi değişkeni değil.
Sınırlayıcı Ölçüm
Bir dizinin uzunluğu derlemede bellidir, ama bu bilgi bir işleve geçmez. Bir dizi parametre olarak alan işlev, dizinin kendisini değil yalnız ilk elemanının adresini alır ve uzunluğu kendi başına bilemez.
// uzunluk.c — dizi uzunlugu bilgisi cagriya gecmez, alan taraf onu ayrica almak zorundadir
#include <stdio.h>
#include <stddef.h>
static int sabit_sayimla(int veri[]) {
/* bu islev dizinin gercek boyutunu bilmiyor: sayida 4 varsayiyor */
int toplam = 0;
for (int i = 0; i < 4; i++) {
toplam += veri[i];
}
return toplam;
}
static int uzunluklu_sayimla(int veri[], size_t uzunluk) {
int toplam = 0;
for (size_t i = 0; i < uzunluk; i++) {
toplam += veri[i];
}
return toplam;
}
int main(void) {
int buyuk[] = {1, 2, 3, 4, 5, 6};
printf("%s %d\n", "sabit varsayimli toplam", sabit_sayimla(buyuk));
printf("%s %d\n", "uzunluk verilen toplam", uzunluklu_sayimla(buyuk, 6));
int kucuk[] = {10, 20, 30};
printf("%s %d\n", "farkli boyutta uzunluk verilen toplam", uzunluklu_sayimla(kucuk, 3));
return 0;
}
sabit varsayimli toplam 10 uzunluk verilen toplam 21 farkli boyutta uzunluk verilen toplam 60
sabit_sayimla, kaynakta int veri[] yazılsa da, çağrıldığında yalnız bir int *
alıyor — dizinin gerçek boyutu hakkında hiçbir bilgi taşımıyor. İşlev döngüsünü sabit
4 sınırıyla yazmış ve buyuk dizisinin ilk dört elemanını toplayıp 10 veriyor —
dizinin gerçek toplamı olan 21den eksik, çünkü son iki eleman hiç görülmüyor.
uzunluklu_sayimla ise uzunluğu ayrı bir parametre (uzunluk) olarak alıyor ve doğru
toplamı (21) üretiyor; aynı işlev kucuk gibi farklı boyuttaki bir diziyle çağrıldığında
da doğru sonucu (60) veriyor, çünkü döngü sınırını kaynakta sabit yazmak yerine
çağırandan alıyor. Bu, Fonksiyonlar dersinde ölçülen “parametre her zaman kopyadır”
kuralının bir dizi için nasıl işlediğini gösteriyor: kopyalanan şey dizinin kendisi
değil, ilk elemanının adresi — ve bu adresin yanında, dizinin eleman sayısı ayrıca
taşınmadıkça kaybolur.
sabit_sayimlanın bildiriminde yazılan int veri[], derleyici tarafından int *veri
olarak yorumlanır — parametre listesindeki bir dizi bildirimi, C’de her zaman bir
işaretçi bildirimine dönüştürülür. Bu yüzden işlev gövdesi içinde sizeof(veri)
yazmak dizinin boyutunu vermez, bir işaretçinin boyutunu verirdi; bu kursta hiçbir
boyut değeri basılmadığı için bu ders o hatayı ölçmüyor, ama sabit_sayimlanın neden
sabit bir sayı (4) varsaymak zorunda kaldığını açıklıyor: işlev, kendisine
geçirilen işaretçiden dizinin gerçek uzunluğunu hiçbir şekilde çıkaramaz. Bu,
yukarıda ölçülen dizi-işaretçi ilişkisinin doğrudan bir sonucu: bir dizi işleve
geçtiğinde geriye yalnız ilk elemanın adresi kalıyor, dizinin kendisine ait hiçbir
üst bilgi (eleman sayısı gibi) bu adresle birlikte taşınmıyor. Standart kitaplığın
dizgi işlevlerinin (strlen dahil) uzunluk bilgisini neden sonlandırıcı bir baytla
kodladığı da aynı kısıttan geliyor: bir işleve yalnız bir başlangıç adresi
geçirilebiliyorsa, verinin nerede bittiğini o veriyle birlikte, bir işaretle taşımak
gerekiyor.
Özet
- Bir dizinin eleman sayısı, kaynaktaki bir sabit ifadeden derlemede üretilir (sabit ifade katlaması); sınıf derleyici.
- Bir dizginin uzunluğu hiçbir yerde saklanmaz, sonlandırıcı
\0baytına ulaşılana kadar taranarak koşumda hesaplanır; sınıf koşum. Bu ders böylece iki sınıfı bir arada taşır. - Sonlandırıcı baytın konumu koşumda değişebilir; aynı bellek alanı, sonlandırıcının yerine göre farklı bir dizgi uzunluğu raporlayabilir.
- Dizi-işaretçi ilişkisi adres basılmadan ölçülebilir: eleman değeri (
*(p + i)iledizi[i]aynı), eşitlik ve eleman cinsinden fark yeterlidir. - Bir dizinin uzunluk bilgisi bir işleve geçmez; alan taraf uzunluğu ayrı bir parametre olarak almadıkça, dizinin gerçek boyutunu kendi başına bilemez.
- Bir dizi parametre olarak bildirildiğinde derleyici tarafından bir işaretçiye dönüştürülür; dizginin sonlandırıcı bayt sözleşmesi, tam da bu yüzden — yalnız bir başlangıç adresiyle çalışmak zorunda kalan işlevlerin uzunluğu veriyle birlikte taşıyabilmesi için — vardır.
Sonraki Adım
Bu derste ölçülen iki veri türü — dizi ve dizgi — hep tek bir değişkenin sınırları
içinde kaldı. Yapılar, Birlikler ve Numaralandırmalar dersi, birden çok değeri tek bir
adın altında toplayan üç yeni düzeneği ölçüyor: bir yapının alanları nasıl yerleşiyor,
bir birlik aynı belleği nasıl paylaşıyor ve bir numaralandırma sabiti nasıl derleme
zamanı sabitine dönüşüyor. Bu derste bir dizinin uzunluğunun kaynakta sabit bir ifadeden
geldiği ölçüldü; sıradaki derste aynı sabit ifade fikri, bir numaralandırma değerinin
nasıl üretildiğini açıklayacak. Diziler ve dizgiler burada hep tek tip bir değer
dizisi taşıyordu — hepsi int ya da hepsi char; yapılar ise farklı tiplerden
değerleri tek bir ad altında toplayacak ve o değerlerin bellekte nasıl yerleştiği de
yine derlemede karara bağlanan bir soru olacak.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.