Dizi, aynı türden elemanların bellekte arka arkaya dizilmiş, sabit boyutlu bir bloğudur. Bu bitişik yerleşim hikâyenin tamamıdır: indekslemenin neden anlık olduğunu, dizilerle göstericilerin neden bu kadar yakın akraba olduğunu ve C'nin size neden sıfırıncı elemanın adresini verip gerisini aritmetikle bulmanıza izin verdiğini açıklar.
Bir Dizi Tanımlamak
Eleman tipi, isim ve köşeli parantez içinde bir boyut:
int scores[5]; // 5 int
double prices[100]; // 100 double
char initials[3]; // 3 char
Boyut eleman sayısıdır ve dizinin tanımlandığı yerde biliniyor olmalıdır - genellikle bir sabit değer ya da #define'lanmış bir sabit. Bellek hemen ayrılır; sonradan bir tahsis yapılmaz.
Yeni tanımlanmış yerel bir dizi sıfır değil çöp içerir:
İkinci satır sıfırlar yazdırabilir, kocaman sayılar yazdırabilir ve çalıştırmalar arasında ya da hata ayıklama ile yayın derlemesi arasında farklılık gösterebilir. Bu değerleri okumak tanımsız davranıştır; okumadan önce ilk değeri verin.
İlk Değer Verme
Hepsi işe yarar birkaç biçim:
Akılda tutulması gereken {0} deyimidir: listelemediğiniz tüm elemanlara sıfır atanır, dolayısıyla tek bir sıfır tüm diziyi sıfırlar. Belirtilmiş ilk değerler ([4] = 99) belirli konumları ayarlayıp gerisini sıfırda bırakmanıza olanak tanır; çoğu boş olan arama tabloları için kullanışlıdır.
Sonradan bir diziye atama yapamazsınız:
int a[3] = {1, 2, 3};
int b[3];
b = a; // hata: bir diziye atama yapılamaz
memcpy(b, a, sizeof a); // bir diziyi böyle kopyalarsınız
İndeksleme Sıfırdan Başlar
İlk eleman arr[0], sonuncusu arr[n-1]'dir. arr[n] diye bir şey yoktur.
Bu son döngüyü çalıştırın ve adreslere bakın: her biri bir öncekinden tam olarak sizeof(int) bayt sonradır. Elemanlar gerçekten uç uca dizilmiştir:
indeks 0 1 2 3 4
+--------+--------+--------+--------+--------+
deger | 88 | 95 | 75 | 60 | 100 |
+--------+--------+--------+--------+--------+
offset +0 +4 +8 +12 +16 bayt
^
&scores[0], `scores` de buna dönüşür
Sıfır tabanlı indeksleme keyfî bir seçim değildir - indeksi başlangıçtan itibaren bir offset hâline getirir ki adres aritmetiğinin ihtiyacı tam olarak budur. scores[i], *(scores + i) olarak tanımlanır; bkz. göstericiler ve diziler.
Uzunluğu Bulmak
C, bir dizinin uzunluğunu çalışma zamanında hiçbir yerde saklamaz, ama derleyici bilir ve sizeof size erişim verir:
sizeof(arr) / sizeof(arr[0]) deyimdir. Sabit yazılmış bir sizeof(double) yerine arr[0]'a bölmek, eleman tipini değiştirdiğinizde satırın çalışmaya devam etmesi demektir.
Tuzak: bu yalnızca dizinin tanımlandığı yerde çalışır. Diziyi bir fonksiyona geçirin, parametre bir göstericidir, dolayısıyla sizeof bunun yerine göstericiyi ölçer - tipik olarak 8 bayt, yani "uzunluk" 1 ya da 2 çıkar. C'de dizi alan her fonksiyonun aynı zamanda bir sayı alması bu yüzdendir:
Bir Dizi Üzerinde Döngü
Standart biçim, 0'dan başlayan ve i < n olduğu sürece dönen bir for döngüsüdür:
i <= n değil, i < n olduğuna dikkat edin. n elemanla geçerli indeksler 0 ile n - 1 arasındadır, dolayısıyla <= fazladan bir geçiş yapar ve sonun ötesini okur. hottest ile coldest'a 0 yerine temps[0]'dan başlangıç değeri vermek de kopyalamaya değer diğer alışkanlıktır - bir maksimumu sıfırdan başlatmak, tamamı negatif veride sessizce bozulur.
Sınır Dışına Çıkmak Tanımsız Davranıştır
C'nin başka dillerden gelenleri şaşırtan kısmı budur. Sınır kontrolü yoktur. Ne derleme zamanında ne de çalışma zamanında. Beş elemanlı bir dizide arr[10] bir hata değildir - derleyicinin hiç yorum yapmadan gerçekleştirdiği bir adres hesabıdır.
int arr[5] = {1, 2, 3, 4, 5};
arr[7] = 99; // dizinin sonundan 8 bayt öteye yazar
int x = arr[-1]; // başlangıcın öncesini okur
Bundan sonra olan tanımsız davranıştır ve belirtileri can sıkıcı biçimde çeşitlidir:
- çalışıyor gibi görünür, çünkü isabet ettiği bellek kullanılmıyordu;
- görünür bir sebep olmadan başka bir değişken değişir;
- program bir segmentation fault ile, muhtemelen çok sonra çöker;
- davranış
-O0ile-O2arasında farklılık gösterir.
"Çalışıyor gibi görünmek" tehlikeli olanıdır, çünkü hata ürüne kadar gider. Üç savunma:
i < nile döngü kurun. Taşmaların çoğu bir döngü koşulundaki bir-eksik/bir-fazla hatalarıdır.- Girdiden gelen indeksleri doğrulayın.
- Geliştirirken bir denetleyiciyle (sanitizer) derleyin.
gcc -fsanitize=address -g prog.ctaşmaların çoğunu, dosyayı, satırı ve ilgili diziyi adlandıran anlık bir rapora dönüştürür.
Diğer Türlerden Diziler
Aynı söz dizimi, yapılar dahil her eleman tipi için çalışır:
char word[6] = "hello"; satırı biraz durup düşünmeye değer: bir C metni, son anlamlı baytı '\0' olan bir char dizisinden ibarettir. Dizinin beş harf için altı yuvaya ihtiyaç duymasının sebebi o sonlandırıcıdır.
Kısaca Değişken Uzunluklu Diziler
C99, boyutu bir çalışma zamanı değeri olan dizilere izin verir:
int n = get_count();
int buffer[n]; // değişken uzunluklu bir dizi
İki uyarı. Boyut, dizi oluşturulduktan sonra sabittir, dolayısıyla bir VLA büyüyebilen bir liste değildir. Ve bellek yığından (stack) gelir, dolayısıyla büyük ya da saldırgan kontrolündeki bir n onu taşırıp programı çökertebilir - Linux çekirdeği dahil pek çok projenin VLA'ları tamamen yasaklamasının sebebi budur. Ayrıca C11 gerçekleştirimleri için desteklenmesi isteğe bağlıdır.
Boyut gerçekten çalışma zamanına kadar bilinmiyorsa, bunun yerine tahsis edin:
arr[i]'nin, arr bir dizi de olsa tahsis edilmiş belleğe gösterici de olsa aynı şekilde okunduğuna dikkat edin - indeksleme söz dizimi umursamaz. Tüm ayrıntılar dinamik bellek sayfasında.
Sıkça Sorulan Sorular
C'de bir dizi nasıl tanımlanır?
Eleman tipini, bir ismi ve köşeli parantez içinde bir boyutu verin: int scores[5]; beş int için yer ayırır. Boyut, dizinin tanımlandığı yerde bilinen bir sabit olmalıdır (değişken uzunluklu diziler dışında) ve siz değer vermedikçe elemanlar ilk değersiz başlar.
C'de bir dizinin uzunluğu nasıl bulunur?
sizeof(arr) / sizeof(arr[0]) - toplam bayt boyutu bölü bir elemanın boyutu. Yalnızca dizinin tanımlandığı kapsamda çalışır: dizi bir fonksiyona geçirildiğinde göstericiye dönüşmüştür ve sizeof bunun yerine göstericiyi ölçer.
C'de bir dizinin sonunu geçerseniz ne olur?
Hiçbir şey sizi durdurmaz. C sınır kontrolü yapmaz, dolayısıyla 5 elemanlı bir dizide arr[10] orada ne bellek varsa onu okur ya da yazar. Bu tanımsız davranıştır: çöp yazdırabilir, başka bir değişkeni bozabilir ya da çökebilir - ve bozulana kadar çalışıyor gibi görünebilir.
C'de bir dizinin tüm elemanlarına nasıl sıfır atanır?
int arr[100] = {0}; - listelemediğiniz tüm elemanlara sıfır atanır, dolayısıyla tek bir sıfır tüm diziyi sıfırlar. C23'te int arr[100] = {}; de çalışır. Hiç ilk değer vermezseniz yerel bir dizinin içeriği belirsiz çöptür.