Pertanyaan ini ditujukan kepada guru C di luar sana:
Di C, dimungkinkan untuk mendeklarasikan pointer sebagai berikut:
char (* p)[10];
.. yang pada dasarnya menyatakan bahwa penunjuk ini menunjuk ke larik 10 karakter. Hal yang rapi tentang mendeklarasikan pointer seperti ini adalah Anda akan mendapatkan kesalahan waktu kompilasi jika Anda mencoba menetapkan pointer dari array dengan ukuran berbeda ke p. Ini juga akan memberi Anda kesalahan waktu kompilasi jika Anda mencoba menetapkan nilai pointer char sederhana ke p. Saya mencoba ini dengan gcc dan tampaknya berfungsi dengan ANSI, C89 dan C99.
Menurut saya, mendeklarasikan pointer seperti ini akan sangat berguna - terutama, saat meneruskan pointer ke suatu fungsi. Biasanya, orang akan menulis prototipe dari fungsi seperti ini:
void foo(char * p, int plen);
Jika Anda mengharapkan buffer dengan ukuran tertentu, Anda cukup menguji nilai plen. Namun, Anda tidak dapat dijamin bahwa orang yang meneruskan p kepada Anda akan benar-benar memberi Anda lokasi memori yang valid di buffer itu. Anda harus percaya bahwa orang yang memanggil fungsi ini melakukan hal yang benar. Di samping itu:
void foo(char (*p)[10]);
..akan memaksa pemanggil untuk memberi Anda buffer dengan ukuran yang ditentukan.
Ini tampaknya sangat berguna tetapi saya belum pernah melihat penunjuk yang dinyatakan seperti ini dalam kode apa pun yang pernah saya temui.
Pertanyaan saya adalah: Apakah ada alasan mengapa orang tidak menyatakan petunjuk seperti ini? Apakah saya tidak melihat beberapa jebakan yang jelas?
10
dapat diganti dengan variabel apa pun dalam cakupanJawaban:
Apa yang Anda katakan di postingan Anda benar sekali. Saya akan mengatakan bahwa setiap pengembang C sampai pada penemuan yang persis sama dan pada kesimpulan yang persis sama ketika (jika) mereka mencapai tingkat kemahiran tertentu dengan bahasa C.
Ketika spesifikasi area aplikasi Anda memanggil larik dengan ukuran tetap tertentu (ukuran larik adalah konstanta waktu kompilasi), satu-satunya cara yang tepat untuk meneruskan larik tersebut ke suatu fungsi adalah dengan menggunakan parameter penunjuk-ke-larik
(dalam bahasa C ++ ini juga dilakukan dengan referensi
).
Ini akan mengaktifkan pemeriksaan tipe tingkat bahasa, yang akan memastikan bahwa larik dengan ukuran yang benar-benar tepat disediakan sebagai argumen. Faktanya, dalam banyak kasus orang menggunakan teknik ini secara implisit, bahkan tanpa menyadarinya, menyembunyikan tipe array di belakang nama typedef.
Perhatikan juga bahwa kode di atas adalah invariant dengan relasi ke
Vector3d
tipe menjadi array atau astruct
. Anda dapat mengganti definisiVector3d
kapan saja dari array ke astruct
dan kembali, dan Anda tidak perlu mengubah deklarasi fungsi. Dalam kedua kasus, fungsi akan menerima objek gabungan "dengan referensi" (ada pengecualian untuk ini, tetapi dalam konteks diskusi ini hal ini benar).Namun, Anda tidak akan melihat metode pengoperan array ini terlalu sering digunakan secara eksplisit, hanya karena terlalu banyak orang yang bingung dengan sintaks yang agak berbelit-belit dan tidak cukup nyaman dengan fitur bahasa C seperti itu untuk menggunakannya dengan benar. Karena alasan ini, dalam kehidupan nyata rata-rata, meneruskan array sebagai penunjuk ke elemen pertamanya adalah pendekatan yang lebih populer. Itu hanya terlihat "lebih sederhana".
Tetapi pada kenyataannya, menggunakan pointer ke elemen pertama untuk pengoperan array adalah teknik yang sangat khusus, sebuah trik, yang melayani tujuan yang sangat spesifik: satu-satunya tujuannya adalah untuk memfasilitasi lintasan yang lewat dengan ukuran yang berbeda (yaitu ukuran run-time) . Jika Anda benar-benar harus dapat memproses array ukuran run-time, maka cara yang tepat untuk meneruskan array tersebut adalah dengan pointer ke elemen pertamanya dengan ukuran konkret yang disediakan oleh parameter tambahan.
Sebenarnya, dalam banyak kasus, sangat berguna untuk dapat memproses array ukuran run-time, yang juga berkontribusi pada popularitas metode. Banyak pengembang C tidak pernah menemukan (atau tidak pernah mengenali) kebutuhan untuk memproses array ukuran tetap, sehingga tetap tidak menyadari teknik ukuran tetap yang tepat.
Namun demikian, jika ukuran array tetap, meneruskannya sebagai pointer ke elemen
adalah kesalahan tingkat teknik utama, yang sayangnya agak meluas akhir-akhir ini. Teknik pointer-to-array adalah pendekatan yang jauh lebih baik dalam kasus seperti itu.
Alasan lain yang mungkin menghalangi pengadopsian teknik passing larik ukuran tetap adalah dominasi pendekatan naif untuk pengetikan larik yang dialokasikan secara dinamis. Misalnya, jika program memanggil array tipe tetap
char[10]
(seperti dalam contoh Anda), rata-rata pengembang akanmalloc
seperti array sepertiLarik ini tidak dapat diteruskan ke fungsi yang dideklarasikan sebagai
yang membingungkan developer rata-rata dan membuat mereka meninggalkan deklarasi parameter ukuran tetap tanpa memikirkannya lebih lanjut. Namun kenyataannya, akar masalahnya terletak pada
malloc
pendekatan yang naif . Themalloc
Format yang ditunjukkan di atas harus disediakan untuk array ukuran run-time. Jika tipe array memiliki ukuran waktu kompilasi, cara yang lebih baik untukmalloc
itu akan terlihat sebagai berikutIni, tentu saja, dapat dengan mudah diteruskan ke pernyataan di atas
foo
dan kompilator akan melakukan pemeriksaan jenis yang benar. Tetapi sekali lagi, ini terlalu membingungkan bagi pengembang C yang tidak siap, itulah sebabnya Anda tidak akan melihatnya terlalu sering dalam kode harian rata-rata yang "biasa".
sumber
const
properti dengan teknik ini. Sebuahconst char (*p)[N]
argumen tampaknya tidak kompatibel dengan pointer kechar table[N];
Sebaliknya, sederhanachar*
ptr tetap kompatibel denganconst char*
argumen.(*p)[i]
dan tidak*p[i]
. Yang terakhir akan melompat sesuai ukuran array, yang hampir pasti bukan yang Anda inginkan. Setidaknya bagi saya, mempelajari sintaksis ini menyebabkan, bukannya mencegah, kesalahan; Saya akan mendapatkan kode yang benar lebih cepat hanya dengan melewati pelampung *.const
penunjuk ke larik elemen yang bisa berubah. Dan ya, ini benar-benar berbeda dari penunjuk ke larik elemen yang tidak dapat diubah.Saya ingin menambahkan jawaban AndreyT (jika ada yang menemukan halaman ini mencari info lebih lanjut tentang topik ini):
Ketika saya mulai bermain lebih banyak dengan deklarasi ini, saya menyadari bahwa ada cacat utama yang terkait dengannya di C (ternyata bukan di C ++). Sangat umum untuk memiliki situasi di mana Anda ingin memberikan penelepon sebuah pointer const ke buffer yang telah Anda tulis. Sayangnya, ini tidak mungkin ketika mendeklarasikan pointer seperti ini di C. Dengan kata lain, standar C (6.7.3 - Paragraph 8) bertentangan dengan hal seperti ini:
Batasan ini tampaknya tidak ada di C ++, membuat jenis deklarasi ini jauh lebih berguna. Tetapi dalam kasus C, perlu untuk kembali ke deklarasi pointer biasa setiap kali Anda ingin pointer const ke buffer ukuran tetap (kecuali buffer itu sendiri dideklarasikan sebagai const). Anda dapat menemukan info lebih lanjut di utas email ini: teks tautan
Ini adalah kendala yang parah menurut saya dan ini bisa menjadi salah satu alasan utama mengapa orang biasanya tidak menyatakan petunjuk seperti ini di C. Yang lainnya adalah fakta bahwa kebanyakan orang bahkan tidak tahu bahwa Anda dapat menyatakan penunjuk seperti ini sebagai AndreyT telah menunjukkan.
sumber
Alasan yang jelas adalah bahwa kode ini tidak dapat dikompilasi:
Promosi default dari sebuah larik adalah ke penunjuk yang tidak memenuhi syarat.
Juga lihat pertanyaan ini , menggunakan
foo(&p)
harus bekerja.sumber
foo(&p)
.Saya juga ingin menggunakan sintaks ini untuk mengaktifkan lebih banyak pemeriksaan tipe.
Tapi saya juga setuju bahwa sintaksis dan model mental menggunakan pointer lebih sederhana, dan lebih mudah diingat.
Berikut beberapa kendala yang saya temui.
Mengakses larik membutuhkan penggunaan
(*p)[]
:Sangat menggoda untuk menggunakan pointer-to-char lokal sebagai gantinya:
Tapi ini sebagian akan menggagalkan tujuan menggunakan tipe yang benar.
Kita harus ingat untuk menggunakan operator address-of saat menetapkan alamat array ke pointer-to-array:
Operator address-of mendapatkan alamat dari seluruh array
&a
, dengan tipe yang benar untuk ditetapkanp
. Tanpa operator,a
secara otomatis diubah ke alamat elemen pertama dari array, sama seperti in&a[0]
, yang memiliki tipe berbeda.Karena konversi otomatis ini sudah berlangsung, saya selalu bingung apakah
&
itu perlu. Hal ini konsisten dengan penggunaan&
pada variabel jenis lain, tetapi saya harus ingat bahwa sebuah array adalah khusus dan saya membutuhkannya&
untuk mendapatkan jenis alamat yang benar, meskipun nilai alamatnya sama.Salah satu alasan untuk masalah saya mungkin karena saya belajar K&R C di tahun 80-an, yang belum mengizinkan penggunaan
&
operator di seluruh larik (meskipun beberapa kompiler mengabaikannya atau mentolerir sintaksis). Ngomong-ngomong, mungkin menjadi alasan lain mengapa pointers-to-array mengalami kesulitan untuk diadopsi: mereka hanya bekerja dengan baik sejak ANSI C, dan&
batasan operator mungkin menjadi alasan lain untuk menganggapnya terlalu canggung.Bila
typedef
ini tidak digunakan untuk membuat jenis untuk array pointer-ke-(dalam file header umum), maka array pointer-to-global yang membutuhkan lebih rumitextern
deklarasi untuk berbagi di file:sumber
Sederhananya, C tidak melakukan hal-hal seperti itu. Sebuah array tipe
T
diteruskan sebagai penunjuk ke yang pertamaT
dalam array, dan hanya itu yang Anda dapatkan.Hal ini memungkinkan untuk beberapa algoritma yang keren dan elegan, seperti perulangan melalui array dengan ekspresi seperti
Sisi negatifnya adalah pengelolaan ukurannya terserah Anda. Sayangnya, kegagalan untuk melakukan ini dengan hati-hati juga telah menyebabkan jutaan bug dalam pengkodean C, dan / atau peluang untuk eksploitasi yang jahat.
Apa yang mendekati apa yang Anda tanyakan di C adalah meneruskan a
struct
(dengan nilai) atau penunjuk ke satu (dengan referensi). Selama tipe struct yang sama digunakan di kedua sisi operasi ini, baik kode yang membagikan referensi dan kode yang menggunakannya sama-sama setuju tentang ukuran data yang ditangani.Struct Anda dapat berisi data apa pun yang Anda inginkan; itu bisa berisi larik Anda dengan ukuran yang ditentukan dengan baik.
Namun, tidak ada yang mencegah Anda atau pembuat kode yang tidak kompeten atau jahat untuk menggunakan cast untuk mengelabui compiler agar memperlakukan struct Anda sebagai salah satu ukuran yang berbeda. Kemampuan yang hampir tidak terbelenggu untuk melakukan hal semacam ini adalah bagian dari desain C.
sumber
Anda dapat mendeklarasikan larik karakter dengan beberapa cara:
Prototipe ke fungsi yang mengambil array berdasarkan nilai adalah:
atau dengan referensi:
atau dengan sintaks array:
sumber
char (*p)[10] = malloc(sizeof *p)
.Saya tidak akan merekomendasikan solusi ini
karena mengaburkan fakta bahwa Vector3D memiliki tipe yang harus Anda ketahui. Pemrogram biasanya tidak mengharapkan variabel dengan tipe yang sama memiliki ukuran yang berbeda. Pertimbangkan:
dimana sizeof a! = sizeof b
sumber
sizeof(a)
tidak samasizeof(b)
?Mungkin saya melewatkan sesuatu, tapi ... karena array adalah pointer konstan, pada dasarnya itu berarti tidak ada gunanya meneruskan pointer ke mereka.
Tidak bisakah kamu menggunakan saja
void foo(char p[10], int plen);
?sumber
Pada kompiler saya (vs2008) itu memperlakukan
char (*p)[10]
sebagai array penunjuk karakter, seolah-olah tidak ada tanda kurung, bahkan jika saya mengkompilasi sebagai file C. Apakah kompilator mendukung "variabel" ini? Jika demikian itu adalah alasan utama untuk tidak menggunakannya.sumber