Menu

C'de Göstericiler ve Diziler: Dönüşüm, arr[i] = *(arr+i) ve sizeof Tuzağı

C'de bir dizi adı, kullandığınız hemen her yerde ilk elemanına bir göstericiye dönüşür. Bu sayfa o dönüşümü, arr[i]'nin neden harfi harfine *(arr+i) olduğunu, dizi boyutunun neden fonksiyonlara ayrı yolculuk etmesi gerektiğini ve bir diziye göstericinin göstericiler dizisinden nasıl farklı olduğunu açıklıyor.

Bu sayfada çalıştırılabilir editörler var - düzenle, çalıştır ve sonucu anında gör.

Diziler ve göstericiler C'de, çoğu zaman aynı şekilde davranan farklı şeylerdir. O "çoğu zaman", konuyu kaygan kılan şeydir: bir diziye gösterici gibi davranan kod genellikle çalışır, ta ki farkın önemli olduğu iki yerden birine düşüp cevabın sessizce yanlış olduğu güne dek.

Bu sayfa çizgiyi kesin biçimde çiziyor.

Dizi Dönüşümü: Her Şeyin Ardındaki Tek Kural

Hemen her ifadede bir dizi adı, otomatik olarak ilk elemanına bir göstericiye dönüştürülür. Buna dönüşüm (decay) denir. arr, int * tipiyle &arr[0] olur.

Dizi adı ile ilk elemanının adresi aynı şekilde yazdırılır. İş başındaki dönüşüm budur ve bir diziyi bir göstericiye & olmadan atayabilmenizin sebebidir.

Dönüşümün gerçekleşmediği tam olarak üç yer vardır: sizeof'un işleneni olarak, &'in işleneni olarak ve bir karakter dizisine bir metin sabitinden ilk değer verirken. Diğer her yerde dönüşümü varsayın.

arr[i] Harfi Harfine *(arr + i)'dir

C standardı indeks operatörünü gösterici aritmetiği cinsinden tanımlar: a[b], *(a + b) demektir. İndeksleme ayrı bir özellik değildir - bir kısaltmadır.

Bundan iki sonuç çıkar. Birincisi, bir gösterici indekslenebilir: p[2], *(p + 2)'dir; gösterici alan fonksiyonların tanıdık arr[i] söz dizimini hâlâ kullanabilmesinin sebebi budur. İkincisi, 2[arr] derlenir - *(2 + arr), *(arr + 2) ile aynı adrestir. Bunu bir sınav dışında kimse yazmaz, ama indekslemenin "gerçekten" gösterici aritmetiği olup olmadığı sorusunu çözer.

Bir Fonksiyona Dizi Geçirmek

Dönüşüm yüzünden bir fonksiyon asla bir dizi almaz. Bir gösterici alır.

O örnekten çıkarılmaya değer üç şey.

int arr[100]'deki boyut belgelemedir, başka bir şey değil. Derleyici parametreyi int *arr olarak yeniden yazar ve 100 tane bir şey geçirdiğinizi asla denetlemez.

Uzunluk ayrı yolculuk etmelidir. Fonksiyonun bir adresi vardır ve verinin ne kadar uzandığı hakkında hiçbir fikri yoktur. Neredeyse her dizi alan C fonksiyonunun bir sayı almasının sebebi budur: memcpy(dst, src, n), fread(ptr, size, count, f), qsort(base, nmemb, size, cmp). Metinler yalnızca kendi son işaretlerini, '\0''ı, taşıdıkları için istisnadır.

Bir gösterici geçirmek ucuzdur ve veri paylaşılır. Dizinin kopyası çıkarılmaz, dolayısıyla fonksiyon çağıranın elemanlarını değiştirebilir - çoğu zaman tam olarak istediğiniz budur:

Salt okunur parametreleri print_all'ın yaptığı gibi const int * işaretleyin. Bu niyeti belgeler ve derleyicinin kazara bir yazmayı yakalamasını sağlar.

sizeof Tuzağı

İşte her C programcısını bir kez yakalayan hata. sizeof, dönüşümün gerçekleşmediği iki yerden biridir - dolayısıyla dizinin bildirildiği yerde gerçek dizi boyutunu, dizinin dönüştüğü her yerde bir göstericinin boyutunu verir.

main içinde sizeof data 40 bayttır ve uzunluk 10 hesaplanır. inspect içinde sizeof arr bir göstericinin boyutudur - 64 bitlik bir makinede 8 - ve "uzunluk" 2 çıkar. Kod özdeş görünür ve sessizce yanlıştır.

Bundan çıkan kural: bir dizinin uzunluğunu yalnızca dizinin bildirildiği kapsamda hesaplayın ve oradan aktarın.

Bir Dizinin &'i Aynı Tip Değildir

Dönüşümün gerçekleşmediği diğer yer & operatörüdür. &arr, ilk elemanına değil tüm diziye bir göstericidir. İkisi de aynı adresi tutar; fark "bir ekle"nin ne anlama geldiğidir.

Aynı başlangıç adresi, farklı adım. arr'ın tipi int *'dır ve bir eleman adımlar; &arr'ın tipi int (*)[5]'tir ve tüm diziyi adımlar. Bu, bir 2B dizinin bir satırını geçirmeyi çalıştıran mekanizmadır ve çok boyutlu diziler sayfasında ele alınıyor.

Diziye Gösterici ile Gösterici Dizisi

Birbirine benzeyen ve zıt anlamlara gelen iki bildirim:

int *p[5];     // int'e 5 göstericiden oluşan dizi
int (*q)[5];   // 5 int'lik bir diziye gösterici

[]'nin önceliği *'dan yüksektir, dolayısıyla int *p[5], "p bir dizidir, göstericilerden" olarak ayrıştırılır. int (*q)[5]'teki parantezler *'ın önce bağlanmasını zorlar: "q bir göstericidir, bir diziye".

int *p[5]                     int (*q)[5]

 p[0] -> [ int ]               q -> [ int | int | int | int | int ]
 p[1] -> [ int ]                     5 elemanlik bitisik tek blok
 p[2] -> [ int ]
 p[3] -> [ int ]
 p[4] -> [ int ]
 bes ayri adres

Gösterici dizisi yaygın olanıdır ve onu zaten kullandınız: char *argv[], her komut satırı argümanı için bir tane olmak üzere metinlere göstericilerden oluşan bir dizidir. Ayrıca yer harcamadan farklı uzunluklarda metinlerden bir liste tutma yolunuzdur.

(*q)[0]'a dikkat edin: diziyi almak için göstericiyi dereferans edin, sonra indeksleyin. Parantezler yine aynı öncelik sebebiyle zorunludur.

Dizilerle Göstericilerin Gerçekten Ayrıştığı Yerler

Bu kısa tabloyu aklınızda tutun, kafa karışıklığı yok olur:

Dizi int arr[10]Gösterici int *p
Nedirbellekte serilmiş 10 intbir adres tutan tek değişken
sizeof40 - tüm blok8 - yalnızca gösterici
Atanabilir mihayır: arr = x; bir hatadırevet: p = x; sorunsuzdur
& şunu verirint (*)[10]int **
Bellek nereden gelirdiziyle birlikte bildirilirnereyi gösterirseniz

Bir dizi adı, adres tutan bir değişken değildir - depolamanın ta kendisidir ve adres istendiğinde hesaplanır. Ona atama yapamamanızın sebebi budur.

Sıkça Sorulan Sorular

C'de diziler ve göstericiler aynı şey midir?

Hayır, ama karıştırmak kolaydır, çünkü bir dizi adı hemen her ifadede ilk elemanına bir göstericiye dönüşür. Dizinin kendisi, boyutu bilinen bir eleman bloğudur; gösterici ise tek bir adres tutan tek bir değişkendir. sizeof ve &, farkın göründüğü iki yerdir.

Bir fonksiyonun içinde sizeof(arr) neden yanlış cevap verir?

Çünkü parametre bir dizi değildir. void f(int arr[]), sessizce void f(int *arr) olarak yeniden yazılır, dolayısıyla sizeof(arr) özgün diziyi değil bir göstericiyi (çoğu makinede 8 bayt) ölçer. Uzunluğu ayrı bir parametre olarak geçirin.

int *p[5] ile int (*p)[5] arasındaki fark nedir?

int *p[5], int'e 5 göstericiden oluşan bir dizidir. int (*p)[5], 5 int'lik bir diziye tek bir göstericidir. Parantezler *'ı önce isme bağlar; onlar olmadan [] kazanır, çünkü önceliği daha yüksektir.

arr[i] gerçekten *(arr + i) ile aynı mıdır?

Evet - standart a[b]'yi *(a + b) olarak tanımlar. Garip görünen i[arr]'ın derlenip çalışmasının sebebi de budur: toplama değişmelidir, dolayısıyla *(i + arr) aynı elemandır. Gerçek kodda asla yazmayın, ama kuralı kanıtlar.

Coddy programming languages illustration

Coddy ile kodlamayı öğren

BAŞLA