İçeriğe geç
academia.sh

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ı \0 baytı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) ile dizi[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.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat