Bir sayının karesini hesaplamak için bu yolu anlayamıyorum


135

Bir sayının karesini hesaplayan bir işlev buldum:

int p(int n) {
    int a[n]; //works on C99 and above
    return (&a)[n] - a;
}

N 2 değerini döndürür . Soru şu, bunu nasıl yapıyor? Biraz testlerden sonra, ben arasında bulundu (&a)[k]ve (&a)[k+1]bir sizeof(a)/ sizeof(int). Neden?


6
Bu bilgiyi nereden bulduğunuzla ilgili bağlantılarınız var mı?
R Sahu

4
int p(n)? Bu bile derleniyor mu?
barak manos

78
Bu harika, şimdi bir daha asla kullanmayın ve onun yerine n * n kullanın ...

26
ya da daha iyisi:int q(int n) { return sizeof (char [n][n]); }
ouah

17
@ouah bu sorunun varsayarsak kodgolf.stackexchange.com/a/43262/967 kullanmıyorum nedeni sizeofkarakterleri kurtarmaktı. Diğer herkes: bu kasıtlı olarak belirsiz bir kod, tanımsız bir davranış, @ ouah'ın cevabı doğru.
ecatmur

Yanıtlar:


117

Açıkçası bir hack ... ama *operatörü kullanmadan bir sayı karesi alma yolu (bu bir kodlama yarışması gereksinimi idi).

(&a)[n] 

intkonumdaki bir işaretçiye eşdeğerdir

(a + sizeof(a[n])*n)

ve böylece tüm ifade

  (&a)[n] -a 

= (a + sizeof(a[n])*n -a) /sizeof(int)

= sizeof(a[n])*n / sizeof(int)
= sizeof(int) * n * n / sizeof(int)
= n * n

11
Ve açıkça ifade ettiğiniz gibi, ancak açık bir şekilde ifade etme ihtiyacını hissediyorum, en iyi ihtimalle bir sözdizimi hack'idir. Çarpma işlemi hala orada olacak; kaçınılması gereken sadece operatördür.
Tommy

Arkasında ne olduğunu anladım, ama asıl sorum (& a) [k] 'nın + k * sizeof (a) / sizeof (int) ile aynı adreste olduğu
Emanuel

33
Eski bir codger olarak ben derleyici tedavi edebilir gerçeğiyle şaşırmış (&a)bir nesneye bir işaretçi olarak n*sizeof(int)ne zaman nderleme zamanında bilinmemektedir. C eskiden basit bir
Floris

Bu oldukça akıllıca bir hack, ama üretim kodunda görmediğiniz bir şey (umarım).
John Odom

14
Bir kenara, aynı zamanda UB'dir, çünkü ne altta yatan dizinin bir öğesini ne de sadece geçmişi işaret edecek bir işaretçiyi arttırır.
Tekilleştirici

86

Bu hack'i anlamak için, önce işaretçi farkını anlamalısınız, yani, aynı dizinin öğelerini işaret eden iki işaretçi çıkarıldığında ne olur ?

Bir işaretçi diğerinden çıkarıldığında, sonuç işaretçiler arasındaki mesafedir (dizi öğelerinde ölçülür). Yani, eğer pişaret eder a[i]ve qişaret ederse a[j], o p - qzaman eşittiri - j .

C11: 6.5.6 Katkı operatörleri (s9):

İki işaretçi çıkarıldığında , her ikisi de aynı dizi nesnesinin öğelerini veya dizi nesnesinin son öğesinin ötesini işaret eder; sonuç, iki dizi öğesinin aboneliklerinin farkıdır . [...].
Başka bir deyişle, ifadeler Pve Qsırasıyla bir dizi nesnesinin i-th ve j-th öğelerini gösteriyorsa , ifadenin (P)-(Q), değerini−j türdeki bir nesneye uyması koşuluyla değeri olur ptrdiff_t.

Şimdi işaretçi için dizi adı dönüşüm farkında olduğunuzu umuyorum, aişaretçi dizinin ilk öğesine dönüştürür a. &atüm bellek bloğunun adresidir, yani bir dizi adresidir a. Aşağıdaki şekil anlamanıza yardımcı olacaktır ( ayrıntılı açıklama için bu cevabı okuyun ):

resim açıklamasını buraya girin

Bu, neden ave &aaynı adrese ve (&a)[i]i th dizisinin adresinin (adresiyle aynı boyutta a) nasıl olduğunu anlamanıza yardımcı olacaktır .

Yani, açıklama

return (&a)[n] - a; 

eşittir

return (&a)[n] - (&a)[0];  

ve bu fark işaretçiler arasındaki (&a)[n]ve her bir elemanın dizisi (&a)[0]olan elemanların sayısını verecektir . Bu nedenle, toplam dizi elemanları = 2'dir . nn intn*nn


NOT:

C11: 6.5.6 Katkı operatörleri (s9):

İki işaretçi çıkarıldığında, her ikisi de aynı dizi nesnesinin öğelerini veya dizi nesnesinin son öğesinin ötesini işaret eder ; sonuç, iki dizi öğesinin aboneliklerinin farkıdır. Sonucun boyutu uygulama tanımlıdır ve türü (işaretli bir tamsayı türü) başlıkta ptrdiff_ttanımlanır <stddef.h>. Sonuç bu tür bir nesnede gösterilemiyorsa, davranış tanımsızdır.

Bu yana (&a)[n]bir dizi objenin son öğe geçen aynı dizi nesne veya bir elemanlarına de nokta, (&a)[n] - aaçılmasına neden olur tanımsız davranış .

Ayrıca işlevin dönüş türünü değiştirmek için daha iyi, dikkat petmek ptrdiff_t.


"her ikisi de aynı dizi nesnesinin öğelerini gösterecektir" - bu da "hack" in UB olup olmadığı sorusunu gündeme getiriyor. İşaretçi aritmetik ifadesi, var olmayan bir nesnenin varsayımsal sonuna atıfta bulunur: Buna bile izin veriliyor mu?
Martin Ba

Özetlemek gerekirse, a, n öğeden oluşan bir dizinin adresidir, bu nedenle & a [0], bu dizideki a öğesiyle aynı olan ilk öğenin adresidir; ek olarak, & a [k] her zaman k öğesinden bağımsız olarak n öğenin bir dizisinin adresi olarak kabul edilir ve & a [1..n] de bir vektör olduğu için öğelerinin "konumu" ardışıktır, yani birinci eleman x konumunda, ikincisi x + konumunda (a'nın n olan vektör elemanlarının sayısı) vb. Haklı mıyım? Ayrıca, bu bir yığın alanıdır, bu yüzden aynı n öğeden yeni bir vektör tahsis edersem, adresinin (& a) [1] ile aynı olduğu anlamına mı gelir?
Emanuel

1
@Emanuel; dizi öğesinin &a[k]bir adresidir . Bu, her zaman bir dizi öğenin adresi olarak kabul edilecektir . Yani, birinci elemanın konumunda olduğu (ya da , ikinci pozisyonundadır) (dizi elemanlarının sayısına + olduğu (bir dizi öğesinin boyutu) *) ve benzerleri. Ve değişken uzunluklu diziler için bellek yığına değil, yığına ayrılır. ka(&a)[k]ka&aaan
haccks

@MartinBa; Buna bile izin var mı? Hayır. İzin verilmiyor. UB'si. Düzenlemeye bakın.
haccks

1
@ doğa ve soru ve takma adınız arasındaki güzel tesadüf
Dimitar Tsonev

35

abir (değişken) dizisidir n int.

&a, (değişken) dizisinin göstergesidir n int.

(&a)[1]bir gösterici intbir intson dizi elemanı geçmiş. Bu işaretçi n intöğeden sonraki öğedir &a[0].

(&a)[2]bir gösterici intbir intiki dizi son dizi elemanı geçmiş. Bu işaretçi 2 * n intöğeden sonraki öğedir &a[0].

(&a)[n]bir gösterici intbir intson dizi elemanı son ndizileri. Bu işaretçi n * n intöğeden sonraki öğedir &a[0]. Sadece çıkart &a[0]ya da asende var n.

Tabii ki bu, (&a)[n]dizinin içinde veya son dizi öğesinin ötesinde (işaretçi aritmetiği C kurallarının gerektirdiği gibi) geçmediği için makinenizde çalışsa bile teknik olarak tanımlanmamış bir davranıştır .


Bunu anladım, ama bu neden C'de oluyor? Bunun arkasındaki mantık nedir?
Emanuel

@Emanuel, işaretçi aritmetiğinin mesafeyi (genellikle bir dizide) ölçmek için yararlı olduğundan daha katı bir cevap yoktur, [n]sözdizimi bir dizi bildirir ve dizileri işaretçilere ayrıştırır. Bu sonuç ile birlikte üç ayrı yararlı şey.
Tommy

1
@Emanuel birisinin bunu neden yapacağını soruyorsanız , eylemin UB doğası nedeniyle olmamak için çok az neden ve her neden var . Ve buna değer belirterek olduğu (&a)[n]türüdür int[n]ve bu şekilde anlatırken kullanılır int*açıklamasında temizlemek değildi durumda ilk elemanın adresi olarak ifade diziler nedeniyle.
WhozCraig

Hayır, neden birinin bunu yapacağını kastetmedim, yani C standardı bu durumda neden böyle davranıyor.
Emanuel

1
@Emanuel Pointer Aritmetiği (ve bu durumda o konunun bir alt bölümü: işaretçi farklılığı ). Bu sitede googling yanı sıra soru ve cevap okumak değer. birçok yararlı faydası vardır ve uygun şekilde kullanıldığında standartlarda somut olarak tanımlanır. Bunu tam olarak kavramak için , listelediğiniz koddaki türlerin nasıl yapıldığını anlamanız gerekir .
WhozCraig

12

Aynı dizinin iki öğesini işaret eden iki işaretçiniz varsa, farkı bu işaretçiler arasındaki öğe sayısını verecektir. Örneğin, bu kod snippet'i 2 çıktısı verir.

int a[10];

int *p1 = &a[1];
int *p2 = &a[3];

printf( "%d\n", p2 - p1 ); 

Şimdi ifadeyi düşünelim

(&a)[n] - a;

Bu ifadede atürü vardır int *ve ilk öğesine işaret eder.

İfadenin &atürü vardır int ( * )[n]ve görüntülenen iki boyutlu dizinin ilk sırasına işaret eder. Değeri, atürlerin farklı olmasına rağmen değeriyle eşleşir .

( &a )[n]

Bu görüntülenen iki boyutlu dizinin n. öğesidir ve türü vardır int[n]Görüntülenen dizinin n. satırdır. İfadede (&a)[n] - a, ilk öğesinin adresine dönüştürülür ve "int *" türüne sahiptir.

Yani arasında (&a)[n]ve an öğesinin n sırası vardır. Yani fark eşit olacaktır n * n.


Her dizinin arkasında n * n boyutunda bir matris var mı?
Emanuel

@Emanuel Bu iki işaretçi arasında bir nxn öğesi matrisi vardır. İşaretçilerin farkı n * n değerine eşittir, yani işaretçiler arasında kaç öğe vardır.
Moskova'dan Vlad

Ama n * n büyüklüğündeki bu matris neden geride? C'de bir kullanımı var mı? Demek istediğim, bilmeden C boyutunda daha fazla dizi "ayırdı" gibi mi? Varsa, bunları kullanabilir miyim? Aksi halde, bu matris neden oluşacaktır (yani, orada olması için bir amacı olmalı).
Emanuel

2
@Emanuel - Bu matris sadece bu durumda işaretçi aritmetiğinin nasıl çalıştığının bir açıklamasıdır. Bu matris tahsis edilmemiştir ve onu kullanamazsınız. Daha önce birkaç kez belirtildiği gibi, 1) bu kod snippet'i pratik kullanımı olmayan bir kesmek; 2) bu hack'i anlamak için işaretçi aritmetiğinin nasıl çalıştığını öğrenmeniz gerekir.
void_ptr

@Emanuel İşaretçi aritmetiğini açıklar. Expreesion (& a) [n], işaretçi aritmetiği nedeniyle görüntülenen iki boyutlu dizinin n elemanına işarettir.
Moskova'dan Vlad

4
Expression     | Value                | Explanation
a              | a                    | point to array of int elements
a[n]           | a + n*sizeof(int)    | refer to n-th element in array of int elements
-------------------------------------------------------------------------------------------------
&a             | a                    | point to array of (n int elements array)
(&a)[n]        | a + n*sizeof(int[n]) | refer to n-th element in array of (n int elements array)
-------------------------------------------------------------------------------------------------
sizeof(int[n]) | n * sizeof(int)      | int[n] is a type of n-int-element array

Böylece,

  1. bir çeşit (&a)[n] isimli int[n]işaretçi
  2. bir çeşit a isimli intişaretçi

Şimdi ifade (&a)[n]-abir işaretçi çıkarma yapar:

  (&a)[n]-a
= ((a + n*sizeof(int[n])) - a) / sizeof(int)
= (n * sizeof(int[n])) / sizeof(int)
= (n * n * sizeof(int)) / sizeof(int)
= n * n
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.