Melalui beberapa pertanyaan wawancara C, saya telah menemukan pertanyaan yang menyatakan "Bagaimana menemukan ukuran array di C tanpa menggunakan sizeof operator?", Dengan solusi berikut. Itu berhasil, tetapi saya tidak mengerti mengapa.
#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;
}
Seperti yang diharapkan, ini mengembalikan 5.
edit: orang-orang menunjukkan jawaban ini , tetapi sintaksnya sedikit berbeda, yaitu metode pengindeksan
size = (&arr)[1] - arr;
jadi saya yakin kedua pertanyaan itu valid dan memiliki pendekatan masalah yang sedikit berbeda. Terima kasih atas bantuan yang sangat besar dan penjelasan yang menyeluruh!
c
arrays
size
language-lawyer
pointer-arithmetic
janojlic.dll
sumber
sumber
&a + 1
tidak menunjuk ke objek yang valid, jadi tidak valid.*((*(&array + 1)) - 1)
aman digunakan untuk mendapatkan elemen terakhir dari larik otomatis? . tl; dr*(&a + 1)
memanggil Undefined Behvaior(ptr)[x]
sama dengan*((ptr) + x)
.Jawaban:
Saat Anda menambahkan 1 ke penunjuk, hasilnya adalah lokasi objek berikutnya dalam urutan objek jenis yang diarahkan ke (yaitu, larik). Jika
p
menunjuk ke suatuint
objek, makap + 1
akan menunjuk ke yang berikutnyaint
secara berurutan. Jikap
menunjuk ke larik 5 elemen dariint
(dalam hal ini, ekspresi&a
), makap + 1
akan menunjuk ke larik 5 elemenint
berikutnya secara berurutan.Mengurangi dua pointer (asalkan keduanya menunjuk ke objek larik yang sama, atau satu menunjuk satu melewati elemen terakhir dari larik) menghasilkan jumlah objek (elemen larik) di antara dua penunjuk tersebut.
Ekspresi
&a
menghasilkan alamata
, dan memiliki tipeint (*)[5]
(penunjuk ke array 5-elemenint
). Ekspresi tersebut&a + 1
menghasilkan alamat dari larik 5 elemenint
berikutnyaa
, dan juga memiliki tipeint (*)[5]
. Ekspresi tersebut*(&a + 1)
membedakan hasil dari&a + 1
, sehingga menghasilkan alamat dari elemen pertamaint
setelah elemen terakhira
, dan memiliki tipeint [5]
, yang dalam konteks ini "meluruh" menjadi ekspresi tipeint *
.Demikian pula, ekspresi
a
"meluruh" menjadi penunjuk ke elemen pertama dari array dan memiliki tipeint *
.Sebuah gambar dapat membantu:
int [5] int (*)[5] int int * +---+ +---+ | | <- &a | | <- a | - | +---+ | | | | <- a + 1 | - | +---+ | | | | | - | +---+ | | | | | - | +---+ | | | | +---+ +---+ | | <- &a + 1 | | <- *(&a + 1) | - | +---+ | | | | | - | +---+ | | | | | - | +---+ | | | | | - | +---+ | | | | +---+ +---+
Ini adalah dua tampilan dari penyimpanan yang sama - di sebelah kiri, kami melihatnya sebagai urutan array 5 elemen
int
, sementara di sebelah kanan, kami melihatnya sebagai urutanint
. Saya juga menunjukkan berbagai ekspresi dan tipenya.Sadarilah, ekspresi
*(&a + 1)
menghasilkan perilaku tidak terdefinisi :C 2011 Online Draft , 6.5.6 / 9
sumber
size = (int*)(&a + 1) - a;
kode ini akan benar-benar valid? : oBaris ini yang paling penting:
size = *(&a + 1) - a;
Seperti yang Anda lihat, alamat tersebut mengambil alamat
a
dan menambahkan satu alamat . Kemudian, itu membedakan penunjuk itu dan mengurangi nilai aslinyaa
darinya.Aritmatika pointer di C menyebabkan ini mengembalikan jumlah elemen dalam array, atau
5
. Menambahkan satu dan&a
merupakan penunjuk ke larik berikutnya dari 5int
detik setelahnyaa
. Setelah itu, kode ini membedakan pointer yang dihasilkan dan mengurangia
(tipe array yang telah meluruh menjadi pointer) dari itu, memberikan jumlah elemen dalam array.Detail tentang cara kerja aritmatika penunjuk:
Katakanlah Anda memiliki penunjuk
xyz
yang menunjuk ke suatuint
tipe dan berisi nilainya(int *)160
. Saat Anda mengurangi angka apa punxyz
, C menentukan bahwa jumlah sebenarnya yang dikurangixyz
adalah angka tersebut dikalikan ukuran jenis yang dituju. Misalnya, jika Anda dikurangi5
darixyz
, nilaixyz
yang dihasilkan akanxyz - (sizeof(*xyz) * 5)
jika aritmetik pointer tidak berlaku.Seperti
a
array5
int
tipe, nilai yang dihasilkan adalah 5. Namun, ini tidak akan bekerja dengan pointer, hanya dengan array. Jika Anda mencoba ini dengan penunjuk, hasilnya akan selalu seperti ini1
.Berikut adalah contoh kecil yang menunjukkan alamat dan bagaimana ini tidak ditentukan. Sisi kiri menunjukkan alamat:
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
Ini berarti bahwa kode tersebut mengurangkan
a
dari&a[5]
(ataua+5
), memberikan5
.Perhatikan bahwa ini adalah perilaku yang tidak ditentukan, dan tidak boleh digunakan dalam keadaan apa pun. Jangan berharap perilaku ini konsisten di semua platform, dan jangan menggunakannya dalam program produksi.
sumber
Hmm, saya curiga ini adalah sesuatu yang tidak akan berhasil di masa-masa awal C. Meskipun pintar.
Mengambil langkah satu per satu:
&a
mendapat penunjuk ke objek berjenis int [5]+1
mendapatkan objek seperti itu berikutnya dengan asumsi ada array dari mereka*
secara efektif mengubah alamat itu menjadi tipe pointer ke int-a
mengurangi dua penunjuk int, mengembalikan jumlah instance int di antara keduanya.Saya tidak yakin itu sepenuhnya legal (dalam hal ini yang saya maksud adalah hukum pengacara bahasa - tidak akan berhasil dalam praktiknya), mengingat beberapa jenis operasi sedang berlangsung. Misalnya Anda hanya "diizinkan" untuk mengurangi dua pointer saat menunjuk ke elemen dalam larik yang sama.
*(&a+1)
disintesis dengan mengakses larik lain, meskipun larik induk, jadi sebenarnya bukan penunjuk ke larik yang sama sepertia
. Selain itu, saat Anda diizinkan untuk mensintesis penunjuk melewati elemen terakhir dari sebuah larik, dan Anda dapat memperlakukan objek apa pun sebagai larik dari 1 elemen, operasi dereferencing (*
) tidak "diizinkan" pada penunjuk yang disintesis ini, meskipun itu tidak memiliki perilaku dalam kasus ini!Saya menduga bahwa pada hari-hari awal C (sintaks K&R, siapa?), Sebuah array membusuk menjadi pointer jauh lebih cepat, jadi
*(&a+1)
mungkin hanya mengembalikan alamat pointer berikutnya tipe int **. Definisi yang lebih ketat dari C ++ modern pasti memungkinkan pointer ke tipe array ada dan mengetahui ukuran array, dan mungkin standar C mengikutinya. Semua kode fungsi C hanya menggunakan pointer sebagai argumen, jadi perbedaan teknis yang terlihat minimal. Tapi saya hanya menebak-nebak di sini.Pertanyaan legalitas terperinci semacam ini biasanya berlaku untuk juru bahasa C, atau alat jenis lint, daripada kode yang dikompilasi. Seorang juru bahasa mungkin mengimplementasikan larik 2D sebagai larik penunjuk ke larik, karena ada satu fitur runtime yang lebih sedikit untuk diterapkan, dalam hal ini dereferensi +1 akan berakibat fatal, dan bahkan jika berhasil akan memberikan jawaban yang salah.
Kelemahan lain yang mungkin adalah kompilator C mungkin menyelaraskan larik terluar. Bayangkan jika ini adalah array 5 chars (
char arr[5]
), ketika program melakukan&a+1
itu memanggil perilaku "array of array". Kompilator mungkin memutuskan bahwa array dari array 5 chars (char arr[][5]
) sebenarnya dibuat sebagai array dari 8 chars (char arr[][8]
), sehingga array luar sejajar dengan baik. Kode yang kita diskusikan sekarang akan melaporkan ukuran array sebagai 8, bukan 5. Saya tidak mengatakan kompiler tertentu pasti akan melakukan ini, tetapi mungkin saja.sumber
sizeof(array)/sizeof(array[0])
memberikan jumlah elemen dalam sebuah array.&a+1
didefinisikan. Seperti yang dicatat oleh John Bollinger,*(&a+1)
tidak, karena ia mencoba untuk membedakan objek yang tidak ada.char [][5]
sebagaichar arr[][8]
. Sebuah array hanyalah objek yang berulang di dalamnya; tidak ada bantalan. Tambahannya, ini akan mematahkan contoh (non-normatif) 2 di C 2018 6.5.3.4 7, yang memberi tahu kita bahwa kita dapat menghitung jumlah elemen dalam array dengansizeof array / sizeof array[0]
.