Tidak dapat memahami cara menghitung kuadrat dari sebuah angka

135

Saya telah menemukan fungsi yang menghitung kuadrat angka:

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

Ini mengembalikan nilai n 2 . Pertanyaannya adalah, bagaimana cara melakukannya? Setelah sedikit pengujian, saya menemukan bahwa antara (&a)[k]dan (&a)[k+1]is sizeof(a)/ sizeof(int). Mengapa demikian?

Emanuel
sumber
6
Apakah Anda memiliki tautan ke tempat Anda menemukan informasi ini?
R Sahu
4
int p(n)? Apakah itu bisa dikompilasi?
barak manos
78
Ini luar biasa, sekarang jangan pernah menggunakannya lagi dan gunakan n * n sebagai gantinya ...
26
atau lebih baik:int q(int n) { return sizeof (char [n][n]); }
ouah
17
@ouah dengan asumsi pertanyaan ini mengacu pada codegolf.stackexchange.com/a/43262/967 alasan saya tidak menggunakan sizeofadalah untuk menyimpan karakter. Semua orang: kode ini sengaja dikaburkan, perilaku tidak terdefinisi, jawaban @ ouah benar.
ecatmur

Jawaban:

117

Jelas hack ... tapi cara mengkuadratkan angka tanpa menggunakan *operator (ini adalah persyaratan kontes pengkodean).

(&a)[n] 

setara dengan penunjuk ke intdi lokasi

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

dan dengan demikian keseluruhan ekspresi itu

  (&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
Mark Lakata
sumber
11
Dan seperti yang Anda jelaskan dengan jelas tetapi saya merasa perlu untuk membuatnya eksplisit, ini adalah peretasan sintaks yang paling baik. Operasi perkalian akan tetap berada di bawah sana; hanya operator yang dihindari.
Tommy
Saya mengerti apa yang terjadi di belakangnya, tetapi pertanyaan saya yang sebenarnya adalah mengapa (& a) [k] berada di alamat yang sama dengan a + k * sizeof (a) / sizeof (int)
Emanuel
33
Sebagai pembuat kode lama saya terkejut dengan fakta bahwa kompilator dapat memperlakukan (&a)sebagai penunjuk ke suatu objek n*sizeof(int)ketika ntidak diketahui pada waktu kompilasi. C dulunya bahasa yang sederhana ...
Floris
Itu peretasan yang cukup pintar, tetapi sesuatu yang tidak akan Anda lihat dalam kode produksi (semoga).
John Odom
14
Selain itu, ini juga UB, karena menambahkan pointer untuk tidak menunjuk ke elemen array yang mendasarinya, atau hanya masa lalu.
Deduplicator
86

Untuk memahami peretasan ini, pertama-tama Anda perlu memahami perbedaan penunjuk, yaitu, apa yang terjadi jika dua penunjuk yang menunjuk ke elemen larik yang sama dikurangi?

Ketika satu pointer dikurangkan dari pointer lainnya, hasilnya adalah jarak (diukur dalam elemen array) antara pointer. Jadi, jika pmenunjuk ke a[i]dan qmenunjuk ke a[j], maka p - qsama dengani - j .

C11: 6.5.6 Operator aditif (p9):

Ketika dua penunjuk dikurangi , keduanya harus menunjuk ke elemen dari objek larik yang sama, atau satu melewati elemen terakhir dari objek larik; hasilnya adalah perbedaan subskrip dari dua elemen array . [...].
Dengan kata lain, jika ekspresi Pdan Qmenunjuk ke, masing-masing, elemen i-th dan j-th dari sebuah objek array, ekspresi tersebut (P)-(Q)memiliki nilaii−j asalkan nilainya sesuai dengan sebuah objek bertipe ptrdiff_t.

Sekarang saya mengharapkan bahwa Anda mengetahui konversi nama array ke pointer, amengubahnya menjadi pointer ke elemen pertama dari array a. &aadalah alamat seluruh blok memori, yaitu alamat array a. Gambar di bawah ini akan membantu Anda untuk memahami ( baca jawaban ini untuk penjelasan rinci ):

masukkan deskripsi gambar di sini

Ini akan membantu Anda untuk memahami mengapa adan &amemiliki alamat yang sama dan bagaimana (&a)[i]alamat dari array ke- i (dengan ukuran yang sama dengan a).

Jadi, pernyataannya

return (&a)[n] - a; 

setara dengan

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

dan perbedaan ini akan memberikan jumlah elemen antara pointer (&a)[n]dan (&a)[0], yang merupakan narray setiap n intelemen. Oleh karena itu, total elemen array adalah n*n= n2 .


CATATAN:

C11: 6.5.6 Operator aditif (p9):

Ketika dua penunjuk dikurangi, keduanya harus menunjuk ke elemen dari objek larik yang sama, atau satu melewati elemen terakhir dari objek larik ; hasilnya adalah perbedaan subskrip dari dua elemen array. Ukuran hasil ditentukan oleh implementasi , dan tipenya (tipe integer bertanda) ptrdiff_tditentukan di <stddef.h>header. Jika hasilnya tidak dapat direpresentasikan dalam objek jenis itu, perilaku tidak ditentukan.

Karena (&a)[n]tidak menunjuk ke elemen dari objek array yang sama atau melewati elemen terakhir dari objek array, tidak (&a)[n] - aakan memunculkan perilaku tidak terdefinisi .

Perhatikan juga bahwa, lebih baik untuk mengubah jenis fungsi yang dikembalikan pmenjadi ptrdiff_t.

haccks
sumber
"keduanya akan menunjuk ke elemen objek larik yang sama" - yang menimbulkan pertanyaan bagi saya apakah "retasan" ini bukan UB sama sekali. Ekspresi aritmatika pointer mengacu pada ujung hipotetis dari objek yang tidak ada: Apakah ini diperbolehkan?
Martin Ba
Untuk meringkas, a adalah alamat dari sebuah larik dari n elemen, jadi & a [0] adalah alamat dari elemen pertama dalam larik ini, yang sama dengan a; Selain itu, & a [k] akan selalu dianggap sebagai alamat larik n elemen, terlepas dari k, dan karena & a [1..n] adalah vektor juga, "lokasi" elemennya berurutan, artinya elemen pertama berada pada posisi x, elemen kedua berada pada posisi x + (banyaknya elemen vektor a yang n) dan seterusnya. Apakah saya benar? Juga, ini adalah ruang heap, jadi apakah itu berarti bahwa jika saya mengalokasikan vektor baru dengan n elemen yang sama, alamatnya sama dengan (& a) [1]?
Emanuel
1
@Tokopedia &a[k]adalah alamat kelemen array a. Itu (&a)[k]akan selalu dianggap sebagai alamat dari sebuah array kelemen. Jadi, elemen pertama adalah pada posisi a(atau &a), kedua adalah pada posisi a+ (jumlah elemen array ayang n) * (ukuran elemen array) dan sebagainya. Dan perhatikan bahwa, memori untuk array panjang variabel dialokasikan di stack, bukan di heap.
haccks
@Tokopedia Apakah ini diperbolehkan? Tidak. Itu tidak diperbolehkan. Ini UB. Lihat edit.
haccks
1
@haccks kebetulan yang bagus antara sifat pertanyaan dan nama panggilan Anda
Dimitar Tsonev
35

aadalah larik (variabel) dari n int.

&aadalah penunjuk ke larik (variabel) dari n int.

(&a)[1]adalah penunjuk intsalah satu intelemen larik terakhir. Pointer ini adalah n intelemen setelahnya &a[0].

(&a)[2]adalah penunjuk intsalah satu intelemen array terakhir dari dua array. Pointer ini adalah 2 * n intelemen setelahnya &a[0].

(&a)[n]adalah penunjuk intsalah satu intelemen array terakhir dari narray. Pointer ini adalah n * n intelemen setelahnya &a[0]. Kurangi saja &a[0]atau adan Anda sudah memilikinya n.

Tentu saja ini secara teknis adalah perilaku yang tidak terdefinisi bahkan jika berfungsi pada mesin Anda karena (&a)[n]tidak menunjuk ke dalam larik atau melewati elemen larik terakhir (seperti yang disyaratkan oleh aturan C aritmatika penunjuk).

ouah
sumber
Baiklah, saya mengerti, tapi mengapa ini terjadi di C? Apa logika dibalik ini?
Emanuel
@Emanuel tidak ada jawaban yang lebih tegas untuk itu selain itu aritmatika pointer berguna untuk mengukur jarak (biasanya dalam array), [n]sintaksis menyatakan array dan array terurai menjadi pointer. Tiga hal yang berguna secara terpisah dengan konsekuensi ini.
Tommy
1
@Emanuel jika Anda bertanya mengapa seseorang melakukan ini, ada sedikit alasan dan setiap alasan untuk tidak melakukannya karena sifat tindakan UB. Dan perlu dicatat bahwa (&a)[n]ini adalah tipe int[n], dan itu mengungkapkan int*karena array yang diekspresikan sebagai alamat elemen pertama mereka, jika itu tidak jelas dalam deskripsi.
WhozCraig
Tidak, saya tidak bermaksud mengapa seseorang melakukan ini, maksud saya mengapa standar C berperilaku seperti ini dalam situasi ini.
Emanuel
1
@Emanuel Pointer Aritmatika (dan dalam hal ini sub-bab dari topik itu: perbedaan penunjuk ). Layak googling sekaligus membaca tanya jawab di situs ini. ia memiliki banyak manfaat yang berguna dan secara konkret didefinisikan dalam standar bila digunakan dengan benar. Untuk memahami sepenuhnya, Anda harus memahami bagaimana tipe dalam kode yang Anda daftarkan dibuat.
WhozCraig
12

Jika Anda memiliki dua pointer yang mengarah ke dua elemen dari array yang sama maka perbedaannya akan menghasilkan jumlah elemen di antara pointer ini. Misalnya cuplikan kode ini akan menghasilkan 2.

int a[10];

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

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

Sekarang mari pertimbangkan ekspresi

(&a)[n] - a;

Dalam ekspresi ini amemiliki tipe int *dan menunjuk ke elemen pertamanya.

Ekspresi &amemiliki tipe int ( * )[n]dan poin ke baris pertama dari larik dua dimensi yang dicitrakan. Nilainya sesuai dengan nilai ameskipun jenisnya berbeda.

( &a )[n]

adalah elemen ke-n dari larik dua dimensi yang dicitrakan dan memiliki tipe. int[n]Itu adalah baris ke-n dari larik yang dicitrakan. Dalam ekspresi (&a)[n] - aitu dikonversi ke alamat elemen pertama dan memiliki tipe `int *.

Jadi di antara (&a)[n]dan aada n baris n elemen. Jadi perbedaannya akan sama n * n.

Vlad dari Moskow
sumber
Jadi di balik setiap array ada matriks dengan ukuran n * n?
Emanuel
@Emanuel Di antara dua pointer ini ada matriks elemen nxn. Dan selisih pointer memberikan nilai sama dengan n * n yaitu berapa banyak elemen diantara pointer tersebut.
Vlad dari Moskow
Tetapi mengapa matriks ini berukuran n * n di belakang? Apakah ada gunanya di C? Maksud saya, ini seperti C "mengalokasikan" lebih banyak array berukuran n, tanpa saya sadari? Jika ya, dapatkah saya menggunakannya? Jika tidak, mengapa matriks ini dibentuk (maksud saya, harus ada tujuan agar matriks ini ada).
Emanuel
2
@Emanuel - Matriks ini hanyalah penjelasan tentang bagaimana aritmatika penunjuk bekerja dalam kasus ini. Matriks ini tidak dialokasikan dan Anda tidak dapat menggunakannya. Seperti telah disebutkan beberapa kali, 1) potongan kode ini adalah peretasan yang tidak praktis digunakan; 2) Anda perlu mempelajari cara kerja aritmatika pointer untuk memahami peretasan ini.
void_ptr
@Emanuel Ini menjelaskan aritmatika penunjuk. Ekspresi (& a) [n] adalah penunjuk ke n- elemen dari larik dua dimensi yang dicitrakan karena aritmatika penunjuk.
Vlad dari Moskow
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

Jadi,

  1. jenisnya (&a)[n]adalah int[n]penunjuk
  2. jenisnya aadalah intpenunjuk

Sekarang ekspresi (&a)[n]-amelakukan substraksi penunjuk:

  (&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
satu-satunya
sumber