Bu kod parçası sizeof () kullanmadan dizi boyutunu nasıl belirler?


134

Bazı C mülakat sorularını inceleyerek, aşağıdaki çözümü kullanarak, "sizeof operatörünü kullanmadan C'deki bir dizinin boyutu nasıl bulunur?" Çalışıyor, ama nedenini anlayamıyorum.

#include <stdio.h>

int main() {
    int a[] = {100, 200, 300, 400, 500};
    int size = 0;

    size = *(&a + 1) - a;
    printf("%d\n", size);

    return 0;
}

Beklendiği gibi, 5 değerini döndürür.

edit: insanlar bu cevabı işaret etti , ancak sözdizimi biraz farklı, yani indeksleme yöntemi

size = (&arr)[1] - arr;

bu yüzden her iki sorunun da geçerli olduğuna ve soruna biraz farklı bir yaklaşım gösterdiğine inanıyorum. Muazzam yardım ve ayrıntılı açıklama için hepinize teşekkür ederim!


13
Eh, bulamıyorum, ama kesinlikle konuşmak gibi görünüyor. Ek J.2 açıkça belirtmektedir: Tekli * operatörünün işleneni geçersiz bir değere sahip , tanımlanmamış bir davranıştır. Burada &a + 1geçerli bir nesneye işaret etmiyor, bu yüzden geçersiz.
Eugene Sh.



@AlmaDo iyi sözdizimi biraz farklıdır, yani indeksleme kısmı, bu yüzden bu sorunun hala kendi başına geçerli olduğuna inanıyorum, ama yanlış olabilir. Bu konuya işaret ettiğiniz için teşekkür ederiz!
janojlic

1
@janojlicz Aslında aynı, çünkü (ptr)[x]aynı *((ptr) + x).
SS Anne

Yanıtlar:


135

Bir işaretçiye 1 eklediğinizde, sonuç bir sonraki nesnenin sivri uçlu tipteki nesneler dizisindeki (yani bir dizi) konumudur. Eğer pbir noktalarından intnesne, daha sonra p + 1bir sonraki işaret eder intbir sırayla. Eğer pbir 5-eleman dizisine noktası int(bu durumda, ifade &a), daha sonra p + 1bir sonraki işaret eder , 5-elemanlı bir dizininint bir sırayla.

İki işaretçi çıkarmak (her ikisi de aynı dizi nesnesine işaret ediyorsa veya biri dizinin son öğesinin ötesine işaret ediyorsa) bu iki işaretçi arasındaki nesne sayısını (dizi öğeleri) verir.

İfade &a, adresini verir ve atürüne sahiptir int (*)[5](işaretçi ile 5 öğeli dizi int). Sentezleme &a + 1sonraki 5-elemanlı bir dizinin adresi verir int, aşağıdaki ave ayrıca türü vardır int (*)[5]. İfade *(&a + 1), sonucunu, bu bağlamda bir tür ifadesine "çürüten" son elemanın son elemanının &a + 1adresini verecek ve türe sahip olacak şekilde deregre eder .intaint [5]int *

Benzer şekilde, ifade adizinin ilk öğesine bir göstergeye "bozulur" ve türü vardır int *.

Bir resim yardımcı olabilir:

int [5]  int (*)[5]     int      int *

+---+                   +---+
|   | <- &a             |   | <- a
| - |                   +---+
|   |                   |   | <- a + 1
| - |                   +---+
|   |                   |   |
| - |                   +---+
|   |                   |   |
| - |                   +---+
|   |                   |   |
+---+                   +---+
|   | <- &a + 1         |   | <- *(&a + 1)
| - |                   +---+
|   |                   |   |
| - |                   +---+
|   |                   |   |
| - |                   +---+
|   |                   |   |
| - |                   +---+
|   |                   |   |
+---+                   +---+

Bu, aynı depolamanın iki görünümüdür - solda, 5 elementli dizilerin intbir dizisi olarak görüyoruz, sağda ise bir dizisi olarak görüyoruz int. Ayrıca çeşitli ifadeleri ve türlerini de gösteriyorum.

Unutmayın, ifade tanımsız davranışla*(&a + 1) sonuçlanır :

...
Eğer sonuç dizi nesnesinin son elemanını geçerse, değerlendirilen tekli * işlecinin işleneni olarak kullanılmamalıdır.

C 2011 Çevrimiçi Taslak , 6.5.6 / 9


13
“Kullanılmayacak” metni resmi: C 2018 6.5.6 8.
Eric Postpischil

@EricPostpischil: 2018 yayın öncesi taslağına bir bağlantınız var mı (N1570.pdf'e benzer)?
John Bode

1
@JohnBode: Bu cevap vardır Wayback Machine bir bağlantı . Satın aldığım kopyadaki resmi standardı kontrol ettim.
Eric Postpischil

7
Yani size = (int*)(&a + 1) - a;bu kod yazılırsa tamamen geçerli olur? : o
Gizmo

@Gizmo muhtemelen başlangıçta bunu yazmadılar çünkü bu şekilde eleman türünü belirtmeniz gerekiyor; orijinal muhtemelen farklı eleman türlerinde tip-jenerik kullanım için bir makro olarak tanımlanmıştır.
Leushenko

35

Bu hat çok önemlidir:

size = *(&a + 1) - a;

Gördüğünüz gibi, ilk önce adresini alır ave bir tanesini ekler. Daha sonra, o işaretçiyi dereferences ve orijinal değerini çıkarır a.

C'deki işaretçi aritmetiği, bunun dizideki öğelerin sayısını döndürmesine neden olur veya 5. Bir ve &a5 intsaniye sonraki dizisine bir işaretçi ekleme a. Bundan sonra, bu kod elde edilen işaretçiyi dereferences ve adizideki öğelerin sayısını vererek (bir işaretçiye çürümüş bir dizi türü) çıkarır .

İşaretçi aritmetiğinin nasıl çalıştığına ilişkin ayrıntılar:

xyzBir inttüre işaret eden ve değeri içeren bir işaretçiniz olduğunu varsayalım (int *)160. Herhangi bir sayıyıxyz , C, çıkarılan gerçek miktarın xyz, o sayının işaret ettiği türün boyutunun katı olduğunu belirtir. Örneğin, çıkarılır eğer 5gelen xyzdeğeri, xyzolurdu çıkan xyz - (sizeof(*xyz) * 5)işaretçi aritmetiği uygulamak olmasaydı.

Gibi a5 int tür dizisi olduğu gibi, sonuç değeri 5 olur. Ancak, bu bir işaretçi ile çalışmaz, sadece bir dizi ile çalışır. Bunu bir işaretçi ile denerseniz, sonuç her zaman olacaktır 1.

Adresleri ve bunun nasıl tanımlanmadığını gösteren küçük bir örnek. Sol taraf adresleri gösterir:

a + 0 | [a[0]] | &a points to this
a + 1 | [a[1]]
a + 2 | [a[2]]
a + 3 | [a[3]]
a + 4 | [a[4]] | end of array
a + 5 | [a[5]] | &a+1 points to this; accessing past array when dereferenced

Kod çıkarılarak olduğu bu araçlar aile ilgili&a[5] (veya a+5vermek) 5.

Bunun tanımsız bir davranış olduğunu ve hiçbir koşulda kullanılmaması gerektiğini unutmayın. Bunun davranışının tüm platformlarda tutarlı olmasını beklemeyin ve üretim programlarında kullanmayın.


27

Hmm, bunun C'nin ilk günlerinde işe yaramayacak bir şey olduğundan şüpheleniyorum.

Adım adım birer adım atmak:

  • &a int türünde bir nesneye işaretçi alır [5]
  • +1 bunlardan bir dizi olduğunu varsayarak bir sonraki nesneyi alır
  • * bu adresi etkin bir şekilde tür işaretçisine int'ye dönüştürür
  • -a iki int işaretçisi çıkarır ve aralarındaki int örneği sayısını döndürür.

Bazı tür operasyonlar göz önüne alındığında, bunun tamamen yasal olduğundan emin değilim (bu, dil-avukat yasaldır - pratikte işe yaramayacak). Örneğin, aynı dizideki öğelere işaret ettiklerinde yalnızca iki işaretçi çıkarmanıza "izin verilir". *(&a+1)bir üst dizi de olsa başka bir diziye erişilerek sentezlendiğinden, aslında aynı diziye bir işaretçi değildir a. Ayrıca, bir işaretçiyi bir dizinin son öğesinden sonra sentezlemenize izin verilirken ve herhangi bir nesneyi 1 öğeden oluşan bir dizi olarak ele alabilirsiniz;* ) bu sentezlenmiş işaretçide "izin verilmez"; bu durumda hiçbir davranışı yoktur!

Ben C (K & R sözdizimi, kimse?) İlk günlerinde, bir dizi çok daha hızlı bir şekilde bir işaretçi çürüdü, bu yüzden *(&a+1)sadece int ** türünde sonraki işaretçi adresini dönebilir şüpheli . Modern C ++ 'ın daha titiz tanımları kesinlikle işaretçinin dizi türünün var olmasına ve dizi boyutunu bilmesine izin verir ve muhtemelen C standartları uymuştur. Tüm C fonksiyon kodu yalnızca işaretçileri bağımsız değişken olarak alır, dolayısıyla teknik görünür fark minimumdur. Ama sadece burada tahmin ediyorum.

Bu tür ayrıntılı yasallık sorusu genellikle derlenmiş kod yerine bir C yorumlayıcısı veya tiftik tipi bir araç için geçerlidir. Bir yorumlayıcı, bir 2D diziyi dizilere bir işaretçi dizisi olarak uygulayabilir, çünkü uygulanacak daha az çalışma zamanı özelliği vardır, bu durumda +1'nin kaydının kaldırılması ölümcül olur ve işe yarasa bile yanlış cevap verir.

Bir başka olası zayıflık, C derleyicisinin dış diziyi hizalaması olabilir. Bunun 5 karakter ( char arr[5]) dizisi olup olmadığını düşünün , program &a+1bunu gerçekleştirdiğinde "dizi dizisi" davranışı çağırıyor. Derleyici , dış char arr[][5]karakter char arr[][8]dizisinin iyi hizalanabilmesi için 5 karakter dizisinden oluşan bir dizinin aslında 8 karakter dizisinden oluşan bir dizi olarak oluşturulmasına karar verebilir . Tartıştığımız kod şimdi dizi boyutunu 5 değil, 8 olarak bildirir. Belirli bir derleyicinin kesinlikle bunu yapacağını söylemiyorum, ama olabilir.


Yeterince adil. Ancak açıklanması zor nedenlerden ötürü, herkes sizeof () / sizeof ()?
Gem Taylor

5
Çoğu insan yapar. Örneğin, sizeof(array)/sizeof(array[0])bir dizideki öğelerin sayısını verir.
SS Anne

C derleyicisinin diziyi hizalamasına izin verilir, ancak bunu yaptıktan sonra dizinin türünü değiştirmesine izin verildiğinden emin değilim. Hizalama, dolgu baytları eklenerek daha gerçekçi bir şekilde gerçekleştirilecektir.
Kevin

1
İşaretçilerin çıkarılması aynı diziye yalnızca iki işaretçi ile sınırlı değildir; işaretçilerin dizinin sonundan bir daha geçmesine de izin verilir. &a+1tanımlanmış. John Bollinger'ın belirttiği gibi, *(&a+1)var olmayan bir nesneyi iptal etmeye çalıştığından değil.
Eric Postpischil

5
Derleyici bir char [][5]as uygulayamaz char arr[][8]. Bir dizi sadece içinde tekrarlanan nesnelerdir; dolgu yok. Ek olarak, bu C 2018 6.5.3.4 7'deki (normatif olmayan) örnek 2'yi kıracaktır, bu da bize bir dizideki eleman sayısını hesaplayabileceğimizi söyler sizeof array / sizeof array[0].
Eric Postpischil
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.