Seperti yang ditunjukkan Joel dalam Stack Overflow podcast # 34 , dalam Bahasa Pemrograman C (alias: K & R), disebutkan properti array ini di C:a[5] == 5[a]
Joel mengatakan bahwa itu karena pointer aritmatika tetapi saya masih tidak mengerti. Kenapa begitua[5] == 5[a]
?
a[1]
sebagai serangkaian token, bukan string: * ({lokasi integer} a {operator} + {integer} 1) sama dengan * ({integer} 1 {operator} + {lokasi integer} a) tetapi tidak sama dengan * ({lokasi integer} a {operator} + {operator} +)char bar[]; int foo[];
danfoo[i][bar]
digunakan sebagai ekspresi.a[b]
=*(a + b)
untuk apa pun yang diberikana
danb
, tapi itu adalah pilihan bebas perancang bahasa untuk+
didefinisikan komutatif untuk semua jenis. Tidak ada yang bisa mencegah mereka dari melarangi + p
sementara mengizinkanp + i
.+
menjadi komutatif, jadi mungkin masalah sebenarnya adalah memilih untuk membuat operasi pointer menyerupai aritmatika, alih-alih merancang operator offset yang terpisah.Jawaban:
Standar C mendefinisikan
[]
operator sebagai berikut:a[b] == *(a + b)
Karena itu
a[5]
akan dievaluasi untuk:dan
5[a]
akan mengevaluasi untuk:a
adalah pointer ke elemen pertama array.a[5]
adalah nilai yang 5 elemen lebih jauh daria
, yang sama dengan*(a + 5)
, dan dari matematika sekolah dasar kita tahu mereka sama (penambahan bersifat komutatif ).sumber
a[5]
akan dikompilasi ke sesuatu sepertimov eax, [ebx+20]
bukannya[ebx+5]
*(10 + (int *)13) != *((int *)10 + 13)
. Dengan kata lain, ada lebih banyak yang terjadi di sini daripada aritmatika sekolah dasar. Commutativity bergantung secara kritis pada kompiler yang mengenali operand mana yang merupakan pointer (dan untuk ukuran objek apa). Dengan kata lain(1 apple + 2 oranges) = (2 oranges + 1 apple)
,, tapi(1 apple + 2 oranges) != (1 orange + 2 apples)
.Karena akses array didefinisikan dalam hal pointer.
a[i]
didefinisikan sebagai mean*(a + i)
, yang komutatif.sumber
*(i + a)
, yang dapat ditulis sebagaii[a]
".*(a + i)
bersifat komutatif". Namun,*(a + i) = *(i + a) = i[a]
karena penambahan bersifat komutatif.Saya pikir ada sesuatu yang terlewatkan oleh jawaban lain.
Ya,
p[i]
secara definisi setara dengan*(p+i)
, yang (karena penambahan komutatif) setara dengan*(i+p)
, yang (sekali lagi, menurut definisi[]
operator) setara dengani[p]
.(Dan dalam
array[i]
, nama array secara implisit dikonversi menjadi pointer ke elemen pertama array.)Tetapi komutatifitas penambahan tidak begitu jelas dalam kasus ini.
Ketika kedua operan adalah dari jenis yang sama, atau bahkan dari jenis numerik yang berbeda yang dipromosikan ke jenis umum, komutatif masuk akal:
x + y == y + x
.Tetapi dalam kasus ini kita berbicara secara spesifik tentang pointer aritmatika, di mana satu operan adalah pointer dan yang lainnya adalah integer. (Integer + integer adalah operasi yang berbeda, dan pointer + pointer tidak masuk akal.)
Deskripsi standar C
+
operator ( N1570 6.5.6) mengatakan:Itu bisa dengan mudah dikatakan:
dalam hal ini keduanya
i + p
dani[p]
akan ilegal.Dalam istilah C ++, kami benar-benar memiliki dua set
+
operator kelebihan beban , yang secara longgar dapat digambarkan sebagai:dan
dimana hanya yang pertama yang benar-benar diperlukan.
Jadi mengapa demikian?
C ++ mewarisi definisi ini dari C, yang mendapatkannya dari B (komutatifitas pengindeksan array secara eksplisit disebutkan dalam referensi pengguna 1972 ke B ), yang mendapatkannya dari BCPL (manual bertanggal 1967), yang mungkin mendapatkannya dari gen bahasa sebelumnya (CPL? Algol?).
Jadi gagasan bahwa pengindeksan array didefinisikan dalam hal penambahan, dan penambahan itu, bahkan penunjuk dan bilangan bulat, adalah komutatif, kembali beberapa dekade, ke bahasa leluhur C.
Bahasa-bahasa itu diketik kurang kuat daripada C modern. Secara khusus, perbedaan antara pointer dan integer sering diabaikan. (Pemrogram C awal kadang-kadang menggunakan pointer sebagai bilangan bulat yang tidak ditandatangani, sebelum
unsigned
kata kunci ditambahkan ke bahasa.) Jadi ide membuat penambahan non-komutatif karena operan dari tipe yang berbeda mungkin tidak akan terjadi pada perancang bahasa tersebut. Jika pengguna ingin menambahkan dua "hal", apakah "hal" itu adalah bilangan bulat, pointer, atau sesuatu yang lain, itu tidak sesuai dengan bahasa untuk mencegahnya.Dan selama bertahun-tahun, setiap perubahan pada aturan itu akan merusak kode yang ada (meskipun standar ANSI C 1989 mungkin merupakan peluang yang baik).
Mengubah C dan / atau C ++ untuk mengharuskan meletakkan pointer di sebelah kiri dan integer di sebelah kanan dapat merusak beberapa kode yang ada, tetapi tidak akan ada kehilangan kekuatan ekspresif nyata.
Jadi sekarang kita memiliki
arr[3]
dan3[arr]
memaknai hal yang persis sama, meskipun bentuk yang terakhir seharusnya tidak pernah muncul di luar IOCCC .sumber
3[arr]
ini adalah artefak yang menarik tetapi jarang digunakan. Jawaban yang diterima untuk pertanyaan ini (< stackoverflow.com/q/1390365/356> ) yang saya tanyakan beberapa waktu lalu telah mengubah cara saya memikirkan sintaksis. Meskipun seringkali secara teknis tidak ada cara yang benar dan salah untuk melakukan hal-hal ini, fitur-fitur ini mulai Anda pikirkan dengan cara yang terpisah dari detail implementasi. Ada manfaat untuk cara berpikir yang berbeda ini yang sebagian hilang ketika Anda terpaku pada detail implementasi.ring16_t
yang memegang 65.535 akan menghasilkanring16_t
nilai dengan 1, terlepas dari ukuranint
.Dan tentu saja
Alasan utama untuk ini adalah bahwa kembali di tahun 70-an ketika C dirancang, komputer tidak memiliki banyak memori (64KB banyak), sehingga kompiler C tidak melakukan banyak pemeriksaan sintaks. Karenanya "
X[Y]
" diterjemahkan agak membabi buta ke "*(X+Y)
"Ini juga menjelaskan sintaksis "
+=
" dan "++
". Segala sesuatu dalam bentuk "A = B + C
" memiliki bentuk kompilasi yang sama. Tetapi, jika B adalah objek yang sama dengan A, maka optimasi tingkat perakitan tersedia. Tetapi kompiler tidak cukup pintar untuk mengenalinya, jadi pengembang harus (A += C
). Demikian pula, jikaC
ada1
, optimasi tingkat perakitan berbeda tersedia, dan sekali lagi pengembang harus membuatnya eksplisit, karena kompiler tidak mengenalinya. (Kompiler yang lebih baru melakukannya, jadi sintaksis itu sebagian besar tidak perlu hari ini)sumber
Satu hal yang sepertinya tidak ada yang menyebutkan tentang masalah Dinah dengan
sizeof
:Anda hanya bisa menambahkan integer ke sebuah pointer, Anda tidak bisa menambahkan dua pointer bersamaan. Dengan begitu ketika menambahkan pointer ke integer, atau integer ke pointer, kompiler selalu tahu bit mana yang memiliki ukuran yang perlu diperhitungkan.
sumber
Untuk menjawab pertanyaan secara harfiah. Tidak selalu benar demikian
x == x
cetakan
sumber
cout << (a[5] == a[5] ? "true" : "false") << endl;
adalahfalse
.x == x
tidak selalu benar). Saya pikir itu niatnya. Jadi dia secara teknis benar (dan mungkin, seperti yang mereka katakan, jenis terbaik yang benar!).NAN
in<math.h>
, yang lebih baik daripada0.0/0.0
, karena0.0/0.0
UB ketika__STDC_IEC_559__
tidak didefinisikan (Sebagian besar implementasi tidak mendefinisikan__STDC_IEC_559__
, tetapi pada sebagian besar implementasi0.0/0.0
akan tetap berfungsi)Saya baru tahu sintaks jelek ini bisa "berguna", atau setidaknya sangat menyenangkan untuk dimainkan ketika Anda ingin berurusan dengan array indeks yang merujuk ke posisi ke dalam array yang sama. Itu dapat menggantikan tanda kurung bersarang dan membuat kode lebih mudah dibaca!
Tentu saja, saya cukup yakin bahwa tidak ada kasus penggunaan untuk itu dalam kode nyata, tetapi saya tetap merasa menarik :)
sumber
i[a][a][a]
Anda berpikir saya adalah penunjuk ke array atau array dari penunjuk ke array atau array ... dana
merupakan indeks. Ketika Anda melihata[a[a[i]]]
, Anda berpikir a adalah pointer ke array atau array dani
merupakan indeks.Pertanyaan / jawaban yang bagus.
Hanya ingin menunjukkan bahwa pointer C dan array tidak sama , meskipun dalam hal ini perbedaannya tidak penting.
Pertimbangkan deklarasi berikut:
Di
a.out
, simbola
berada di alamat yang merupakan awal array, dan simbolp
berada di alamat tempat pointer disimpan, dan nilai pointer di lokasi memori itu adalah awal dari array.sumber
int a[10]
adalah sebuah pointer yang disebut 'a', yang menunjuk ke toko yang cukup untuk 10 integer, di tempat lain. Jadi a + i dan j + i memiliki bentuk yang sama: tambahkan konten dari beberapa lokasi memori. Bahkan, saya pikir BCPL tidak ada artinya, jadi mereka identik. Dan penskalaan tipe sizeof tidak berlaku, karena BCPL murni berorientasi pada kata (pada mesin yang dialamatkan juga kata).int*p = a;
keint b = 5;
Dalam yang terakhir, "b" dan "5" keduanya bilangan bulat, tetapi "b" adalah variabel, sedangkan "5" adalah nilai tetap. Demikian pula, "p" & "a" keduanya alamat karakter, tetapi "a" adalah nilai tetap.Untuk petunjuk dalam C, kami punya
dan juga
Karena itu memang benar demikian
a[5] == 5[a].
sumber
Bukan jawaban, tapi hanya makanan untuk dipikirkan. Jika kelas memiliki operator indeks / subskrip kelebihan, ekspresi
0[x]
tidak akan berfungsi:Karena kami tidak memiliki akses ke kelas int , ini tidak dapat dilakukan:
sumber
class Sub { public: int operator[](size_t nIndex) const { return 0; } friend int operator[](size_t nIndex, const Sub& This) { return 0; } };
operator[]
Akan menjadi fungsi anggota tidak statis dengan tepat satu parameter." Saya terbiasa dengan pembatasan ituoperator=
, tidak berpikir itu berlaku[]
.[]
operator, itu tidak akan pernah menjadi setara lagi ... jikaa[b]
sama dengan*(a + b)
dan Anda mengubah ini, Anda harus kelebihan beban jugaint::operator[](const Sub&);
danint
bukan kelas ...Ini memiliki penjelasan yang sangat baik dalam A TUTORIAL ON POINTERS DAN ARRAYS IN C oleh Ted Jensen.
Ted Jensen menjelaskannya sebagai:
sumber
Saya tahu pertanyaannya dijawab, tetapi saya tidak tahan untuk tidak membagikan penjelasan ini.
Saya ingat desain Principles of Compiler, Mari kita asumsikan
a
adalahint
array dan ukurannyaint
adalah 2 byte, & Base addressa
adalah 1000.Bagaimana cara
a[5]
kerjanya ->Begitu,
Demikian pula ketika kode c dipecah menjadi kode 3-alamat,
5[a]
akan menjadi ->Jadi pada dasarnya kedua pernyataan menunjuk ke lokasi yang sama di memori dan karenanya
a[5] = 5[a]
,.Penjelasan ini juga merupakan alasan mengapa indeks negatif dalam array bekerja di C.
yaitu jika saya mengaksesnya
a[-5]
akan memberi sayaIni akan mengembalikan saya objek di lokasi 990.
sumber
Dalam C array ,
arr[3]
dan3[arr]
adalah sama, dan notasi pointer mereka setara yang*(arr + 3)
ke*(3 + arr)
. Tetapi sebaliknya[arr]3
atau[3]arr
tidak benar dan akan menghasilkan kesalahan sintaksis, karena(arr + 3)*
dan(3 + arr)*
bukan ekspresi yang valid. Alasannya adalah operator dereference harus ditempatkan sebelum alamat yang dihasilkan oleh ekspresi, bukan setelah alamat.sumber
dalam c compiler
ada berbagai cara untuk merujuk ke elemen dalam array! (BUKAN SEMUA WEIRD)
sumber
Sedikit sejarah sekarang. Di antara bahasa-bahasa lain, BCPL memiliki pengaruh yang cukup besar pada perkembangan awal C. Jika Anda mendeklarasikan array di BCPL dengan sesuatu seperti:
yang sebenarnya mengalokasikan 11 kata memori, bukan 10. Biasanya V adalah yang pertama, dan berisi alamat dari kata berikut segera. Jadi tidak seperti C, penamaan V pergi ke lokasi itu dan mengambil alamat elemen zeroeth dari array. Oleh karena itu array tidak langsung dalam BCPL, dinyatakan sebagai
benar-benar harus melakukan
J = !(V + 5)
(menggunakan sintaks BCPL) karena itu perlu untuk mengambil V untuk mendapatkan alamat basis dari array. JadiV!5
dan5!V
itu identik. Sebagai pengamatan anekdotal, WAFL (Warwick Functional Language) ditulis dalam BCPL, dan yang terbaik dari ingatan saya cenderung menggunakan sintaks yang terakhir daripada yang sebelumnya untuk mengakses node yang digunakan sebagai penyimpanan data. Memang ini dari suatu tempat antara 35 dan 40 tahun yang lalu, jadi ingatanku agak berkarat. :)Inovasi mengeluarkan kata penyimpanan ekstra dan meminta kompiler memasukkan alamat dasar array ketika dinamai datang kemudian. Menurut makalah sejarah C ini terjadi pada sekitar waktu struktur ditambahkan ke C.
Perhatikan bahwa
!
dalam BCPL adalah operator awalan unary dan operator infiks biner, dalam kedua kasus melakukan tipuan. Hanya saja bentuk biner termasuk penambahan dua operan sebelum melakukan tipuan. Mengingat sifat berorientasi kata BCPL (dan B) ini sebenarnya membuat banyak akal. Pembatasan "pointer dan integer" dibuat perlu di C ketika memperoleh tipe data, dansizeof
menjadi sesuatu.sumber
Ya, ini adalah fitur yang hanya mungkin karena dukungan bahasa.
Compiler mengartikan
a[i]
sebagai*(a+i)
dan ekspresi5[a]
dievaluasi untuk*(5+a)
. Karena penambahan bersifat komutatif, ternyata keduanya sama. Karenanya ekspresi dievaluasi menjaditrue
.sumber
Dalam C
Pointer adalah "variabel"
nama array adalah "mnemonic" atau "sinonim"
p++;
valid tetapia++
tidak valida[2]
sama dengan 2 [a] karena operasi internal pada keduanya adalah"Aritmatika Pointer" dihitung secara internal sebagai
*(a+3)
sama dengan*(3+a)
sumber
jenis penunjuk
1) pointer ke data
2) const pointer ke data
3) pointer pointer ke data const
dan array adalah tipe (2) dari daftar kami.
Ketika Anda mendefinisikan sebuah array pada suatu waktu, satu alamat diinisialisasi dalam pointer tersebut.
Seperti yang kita tahu bahwa kita tidak dapat mengubah atau memodifikasi nilai const dalam program kita karena itu melemparkan ERROR saat dikompilasi. waktu
Perbedaan utama yang saya temukan adalah ...
Kami dapat menginisialisasi ulang pointer dengan alamat tetapi tidak dengan case yang sama dengan array.
======
dan kembali ke pertanyaan Anda ...
a[5]
tidak lain adalah*(a + 5)
Anda dapat memahami dengan mudah dengan
a
- berisi alamat (orang menyebutnya sebagai alamat dasar) seperti jenis penunjuk (2) dalam daftar kami[]
- operator itu dapat diganti dengan pointer*
.jadi akhirnya ...
sumber