Tidak dapat memahami cara menghitung kuadrat suatu bilangan

Saya telah menemukan fungsi yang menghitung kuadrat suatu bilangan:

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

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


person Emanuel    schedule 07.01.2015    source sumber
comment
Apakah Anda memiliki tautan ke tempat Anda menemukan informasi ini?   -  person R Sahu    schedule 08.01.2015
comment
int p(n)? Apakah itu bisa dikompilasi?   -  person barak manos    schedule 08.01.2015
comment
@barakmanos Ya. ideone.com/QRHvxf   -  person Paul Roub    schedule 08.01.2015
comment
@barakmanos dikompilasi dengan baik di gcc.   -  person lurker    schedule 08.01.2015
comment
Ini luar biasa, sekarang jangan pernah menggunakannya lagi dan gunakan n*n sebagai gantinya...   -  person    schedule 08.01.2015
comment
atau lebih baik: int q(int n) { return sizeof (char [n][n]); }   -  person ouah    schedule 08.01.2015
comment
@ouah - itu adalah jawaban yang sama dengan yang saya posting di kode golf tahun lalu (codegolf.stackexchange.com/a/18283 /14485)   -  person Mark Lakata    schedule 08.01.2015
comment
Bahasa C mencerminkan banyak fitur PDP-11. Misalnya, operator ++/-- cocok dengan mode pengalamatan. Dalam PDP dan [khususnya] VAX, semua jenis operasi aritmatika dapat dilakukan menggunakan mode pengalamatan. Dari situlah asal muasal trik seperti ini.   -  person user3344003    schedule 08.01.2015
comment
sepertinya berasal dari kontes kode C yang samar, jangan pernah melakukan hal seperti itu dalam kode produksi!   -  person Arne Burmeister    schedule 08.01.2015
comment
@ouah dengan asumsi pertanyaan ini merujuk pada codegolf.stackexchange.com/a/43262/967 alasan saya tidak melakukannya gunakan sizeof untuk menyimpan karakter. Yang lainnya: ini adalah kode yang sengaja dikaburkan, perilakunya tidak terdefinisi, jawaban @ouah benar.   -  person ecatmur    schedule 08.01.2015
comment
Aritmatika penunjuk dalam kode ini memunculkan perilaku tidak terdefinisi.   -  person Rufflewind    schedule 08.01.2015
comment
Akan lebih kecil kemungkinannya untuk meluap jika arraynya bertipe char daripada int.   -  person Adrian McCarthy    schedule 08.01.2015
comment
inilah naga: mendapatkan pointer di luar batas array (kecuali satu posisi setelah yang terakhir) adalah Perilaku Tidak Terdefinisi, bahkan jika Anda tidak menghormatinya.   -  person bolov    schedule 09.01.2015
comment
Bukankah ini akan memakan memori ketika Anda menggunakan nilai n yang besar, meskipun hanya sesaat?   -  person Octopus    schedule 09.01.2015
comment
Bagus. A O(n^2) untuk menghitung n^2   -  person Khaled.K    schedule 14.01.2015


Jawaban (5)


Jelas sekali sebuah hack... tapi cara mengkuadratkan suatu bilangan tanpa menggunakan operator * (ini adalah persyaratan kontes coding).

(&a)[n] 

setara dengan penunjuk ke int di lokasi

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

dan dengan demikian keseluruhan ekspresinya adalah

  (&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
person Mark Lakata    schedule 07.01.2015
comment
Dan seperti yang Anda nyatakan dengan jelas tetapi saya merasa perlu untuk membuatnya eksplisit, ini adalah peretasan sintaksis yang terbaik. Operasi perkalian akan tetap ada di bawah sana; hanya operatornya saja yang dihindari. - person Tommy; 08.01.2015
comment
Saya mengerti apa yang terjadi di baliknya, tetapi pertanyaan saya yang sebenarnya adalah mengapa (&a)[k] berada di alamat yang sama dengan a + k * sizeof(a) / sizeof(int) - person Emanuel; 08.01.2015
comment
Sebagai pembuat kode lama, saya terkejut dengan kenyataan bahwa kompiler dapat memperlakukan (&a) sebagai penunjuk ke objek n*sizeof(int) ketika n tidak diketahui pada waktu kompilasi. C dulunya adalah bahasa sederhana... - person Floris; 08.01.2015
comment
Itu peretasan yang cukup pintar, tetapi sesuatu yang tidak akan Anda lihat di kode produksi (semoga). - person John Odom; 08.01.2015
comment
Selain itu, ini juga UB, karena ia menambah pointer agar tidak menunjuk ke elemen array yang mendasarinya, atau hanya melewatinya. - person Deduplicator; 08.01.2015
comment
@Floris: Memang benar, dan ini (bersama beberapa lainnya) menjadi alasan yang sangat kuat mengapa banyak orang masih menggunakan C90 bahkan hingga hari ini. - person alecov; 08.01.2015
comment
@haccks - Ini diambil dari tantangan codegolf untuk menulis fungsi yang mengkuadratkan angka tanpa menggunakan simbol * dalam kode. Anda dapat memutuskan apa maksudnya. Ini tantangan yang konyol. - person Mark Lakata; 09.01.2015
comment
@MarkLakata; Operator * memiliki lebih dari satu arti dalam C. Anda harus menyatakan secara spesifik tentang operator perkalian. Sekilas saya mengira Anda sedang berbicara tentang operator tipuan tetapi mengerti ketika mengunjungi tautannya. - person haccks; 09.01.2015
comment
Dan yang lebih penting, ini tidak menghilangkan perkalian, itu hanya menyebabkan kompiler menyembunyikannya dari Anda. Solusi yang lebih jujur ​​terhadap tantangan ini adalah dengan menulis fungsi yang mensimulasikan perkalian bilangan biner paling sederhana menggunakan shift dan penjumlahan. - person ddyer; 14.01.2015

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

Ketika satu pointer dikurangi dari yang lain, hasilnya adalah jarak (diukur dalam elemen array) antara pointer. Jadi, jika p menunjuk ke a[i] dan q menunjuk ke a[j], maka p - q sama dengan i - j.

C11: 6.5.6 Operator aditif (p9):

Ketika dua pointer dikurangi, keduanya akan menunjuk ke elemen objek array yang sama, atau yang melewati elemen terakhir objek array; hasilnya adalah selisih subskrip kedua elemen array. [...].
Dengan kata lain, jika ekspresi P dan Q masing-masing menunjuk ke elemen ke-9_ dan ke-j dari objek array, ekspresi (P)-(Q) memiliki nilai i−j asalkan nilainya cocok dengan objek bertipe ptrdiff_t.

Sekarang saya berharap Anda mengetahui konversi nama array menjadi pointer, a mengkonversi ke pointer ke elemen pertama array a. &a adalah alamat seluruh blok memori, yaitu alamat array a. Gambar di bawah akan membantu Anda memahami (baca jawaban ini untuk penjelasan detail):

masukkan deskripsi gambar di sini

Ini akan membantu Anda memahami mengapa a dan &a memiliki alamat yang sama dan bagaimana (&a)[i] merupakan alamat 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 n array yang masing-masing berisi n int elemen. Oleh karena itu, total elemen array adalah n*n = n2.


CATATAN:

C11: 6.5.6 Operator aditif (p9):

Ketika dua pointer dikurangi, keduanya akan menunjuk ke elemen objek array yang sama, atau yang melewati elemen terakhir objek array; hasilnya adalah selisih subskrip kedua elemen array. Ukuran hasilnya ditentukan oleh implementasi, dan tipenya (tipe bilangan bulat bertanda) adalah ptrdiff_t yang ditentukan dalam header <stddef.h>. Jika hasilnya tidak dapat direpresentasikan dalam objek bertipe tersebut, perilakunya tidak terdefinisi.

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

Perhatikan juga bahwa, lebih baik mengubah tipe pengembalian fungsi p menjadi ptrdiff_t.

person haccks    schedule 07.01.2015
comment
keduanya akan menunjuk ke elemen objek array yang sama - yang menimbulkan pertanyaan bagi saya apakah peretasan ini bukan UB. Ekspresi aritmatika penunjuk mengacu pada akhir hipotetis dari objek yang tidak ada: Apakah ini diperbolehkan? - person Martin Ba; 08.01.2015
comment
Ringkasnya, a adalah alamat array yang terdiri dari n elemen, jadi &a[0] adalah alamat elemen pertama dalam array ini, yang sama dengan a; selain itu, &a[k] akan selalu dianggap sebagai alamat array yang terdiri dari n elemen, terlepas dari k, dan karena &a[1..n] juga merupakan vektor, lokasi elemen-elemennya berurutan, artinya elemen pertama berada pada posisi x, kedua pada posisi x + (banyaknya elemen vektor a yaitu n) dan seterusnya. Apakah saya benar? Selain itu, ini adalah ruang heap, jadi apakah ini berarti jika saya mengalokasikan vektor baru dari n elemen yang sama, alamatnya sama dengan (&a)[1]? - person Emanuel; 08.01.2015
comment
@Emanuel; &a[k] adalah alamat elemen ke k dari array a. Ini adalah (&a)[k] yang akan selalu dianggap sebagai alamat array elemen k. Jadi, elemen pertama berada pada posisi a (atau &a), elemen kedua berada pada posisi a + (jumlah elemen array a yaitu n)*(ukuran elemen array) dan seterusnya. Dan perhatikan bahwa, memori untuk array dengan panjang variabel dialokasikan di stack, bukan di heap. - person haccks; 08.01.2015
comment
@MartinBa; Apakah ini diperbolehkan? Tidak. Itu tidak diperbolehkan. Itu UB. Lihat hasil editnya. - person haccks; 08.01.2015
comment
Jadi apakah (&a)[n]-(&a)[0] merupakan cara yang benar untuk melakukan ini? - person Floris; 08.01.2015
comment
@Floris; Tidak. (&a)[n] melanggar aturan standar yang mengatakan: keduanya harus menunjuk ke elemen objek array yang sama, atau yang melewati elemen terakhir objek array. Anda dapat melakukan ini dengan cara yang benar dengan mengubah isi fungsi menjadi return sizeof (char [n][n]); seperti yang disarankan di komentar ini. - person haccks; 08.01.2015
comment
@haccks kebetulan yang bagus antara sifat pertanyaan dan nama panggilan Anda - person Dimitar Tsonev; 14.01.2015
comment
@DimitarTsonev; Oh! Ya :) - person haccks; 14.01.2015
comment
Saya rasa saya tidak mengerti mengapa Anda mengatakan kami di luar spesifikasi. Bukankah dalam hal ini kita melewati elemen terakhir dari objek array (elemen terakhir adalah (&a)[n-1], dan kita menanyakan (&a)[n], jadi satu melewati ) - person Falanwe; 14.01.2015
comment
@Falanwe; Elemen terakhir adalah a[n-1]. a[n] melewati elemen terakhir array. (&a)[1] adalah penunjuk untuk melewati elemen terakhir (array n int) dari array. - person haccks; 14.01.2015

a adalah larik (variabel) dari n int.

&a adalah penunjuk ke array (variabel) n int.

(&a)[1] adalah penunjuk int satu int melewati elemen array terakhir. Pointer ini adalah n int elemen setelah &a[0].

(&a)[2] adalah penunjuk int satu int yang melewati elemen larik terakhir dari dua larik. Pointer ini adalah 2 * n int elemen setelah &a[0].

(&a)[n] adalah penunjuk int satu int yang melewati elemen larik terakhir dari larik n. Pointer ini adalah n * n int elemen setelah &a[0]. Kurangi saja &a[0] atau a dan Anda mendapatkan n.

Tentu saja ini adalah perilaku yang secara teknis tidak terdefinisi meskipun ini berfungsi di mesin Anda karena (&a)[n] tidak menunjuk ke dalam array atau melewati elemen array terakhir (seperti yang disyaratkan oleh aturan C aritmatika pointer).

person ouah    schedule 07.01.2015
comment
Ya, saya mengerti, tetapi mengapa ini terjadi di C? Apa logika di balik ini? - person Emanuel; 08.01.2015
comment
@Emanuel tidak ada jawaban yang lebih ketat selain itu aritmatika pointer berguna untuk mengukur jarak (biasanya dalam array), sintaks [n] mendeklarasikan array dan array didekomposisi menjadi pointer. Tiga hal yang berguna secara terpisah dengan konsekuensi ini. - person Tommy; 08.01.2015
comment
@Emanuel jika Anda bertanya mengapa seseorang melakukan ini, hanya ada sedikit alasan dan banyak alasan tidak karena sifat tindakan UB. Dan perlu diperhatikan bahwa (&a)[n] bertipe int[n], dan yang dinyatakan sebagai int* karena array dinyatakan sebagai alamat elemen pertamanya, jika deskripsinya tidak jelas. - person WhozCraig; 08.01.2015
comment
Tidak, maksud saya bukan mengapa seseorang melakukan ini, maksud saya mengapa standar C berperilaku seperti ini dalam situasi ini. - person Emanuel; 08.01.2015
comment
@Emanuel Aritmatika Pointer (dan dalam hal ini sub-bab dari topik tersebut: diferensiasi pointer). Ada baiknya googling sekaligus membaca tanya jawab di situs ini. ini memiliki banyak manfaat yang berguna dan didefinisikan secara konkret dalam standar bila digunakan dengan benar. Untuk memahaminya sepenuhnya, Anda harus memahami bagaimana tipe dalam kode yang Anda daftarkan dibuat. - person WhozCraig; 08.01.2015

Jika Anda memiliki dua pointer yang menunjuk ke dua elemen dari array yang sama maka perbedaannya akan menghasilkan jumlah elemen di antara pointer tersebut. 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 a bertipe int * dan menunjuk ke elemen pertamanya.

Ekspresi &a memiliki tipe int ( * )[n] dan menunjuk ke baris pertama dari gambar array dua dimensi. Nilainya cocok dengan nilai a meskipun tipenya berbeda.

( &a )[n]

adalah elemen ke-n dari array dua dimensi yang dicitrakan ini dan memiliki tipe int[n] Yaitu baris ke-n dari array yang dicitrakan. Dalam ekspresi (&a)[n] - a diubah ke alamat elemen pertamanya dan bertipe `int *.

Jadi antara (&a)[n] dan a ada n baris n elemen. Jadi selisihnya akan sama dengan n * n.

person Vlad from Moscow    schedule 07.01.2015
comment
Jadi di balik setiap array ada matriks berukuran n*n? - person Emanuel; 08.01.2015
comment
@Emanuel Di antara dua petunjuk ini ada matriks elemen n x n. Dan selisih pointernya memberikan nilai sama dengan n * n yaitu berapa banyak elemen yang ada di antara pointer tersebut. - person Vlad from Moscow; 08.01.2015
comment
Tetapi mengapa matriks berukuran n*n ini tertinggal? Apakah ada gunanya di C? Maksud saya, ini seperti C mengalokasikan lebih banyak array dengan ukuran n, tanpa saya sadari? Jika ya, bisakah saya menggunakannya? Jika tidak, mengapa matriks ini dibentuk (maksud saya, matriks ini pasti ada tujuannya). - person Emanuel; 08.01.2015
comment
@Emanuel - Matriks ini hanya penjelasan tentang cara kerja aritmatika pointer dalam kasus ini. Matriks ini tidak dialokasikan dan Anda tidak dapat menggunakannya. Seperti yang telah disebutkan beberapa kali, 1) cuplikan kode ini adalah peretasan yang tidak memiliki kegunaan praktis; 2) Anda perlu mempelajari cara kerja aritmatika penunjuk untuk memahami peretasan ini. - person void_ptr; 08.01.2015
comment
@Emanuel Ini menjelaskan aritmatika pointer. Ekspresi ( &a )[n] adalah penunjuk ke n- elemen array dua dimensi yang dicitrakan karena aritmatika penunjuk. - person Vlad from Moscow; 08.01.2015

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

Dengan demikian,

  1. jenis (&a)[n] adalah int[n] penunjuk
  2. jenis a adalah int penunjuk

Sekarang ekspresi (&a)[n]-a melakukan pengurangan pointer:

  (&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
person onlyice    schedule 14.01.2015