Inilah yang saya temukan selama masa belajar saya:
#include<iostream>
using namespace std;
int dis(char a[1])
{
int length = strlen(a);
char c = a[2];
return length;
}
int main()
{
char b[4] = "abc";
int c = dis(b);
cout << c;
return 0;
}
Jadi dalam variabel int dis(char a[1])
, [1]
sepertinya tidak melakukan apa-apa dan tidak berfungsi sama
sekali, karena saya dapat menggunakan a[2]
. Hanya suka int a[]
atau char *a
. Saya tahu nama array adalah pointer dan cara menyampaikan array, jadi teka-teki saya bukan tentang bagian ini.
Yang ingin saya ketahui adalah mengapa kompiler mengizinkan perilaku ini ( int a[1]
). Atau apakah ada makna lain yang tidak saya ketahui?
typedef
tipe array. Jadi "pembusukan untuk pointer" di jenis argumen bukan hanya gula sintaksis mengganti[]
dengan*
, itu benar-benar akan melalui sistem jenis. Ini memiliki konsekuensi dunia nyata untuk beberapa tipe standar sepertiva_list
yang dapat didefinisikan dengan tipe array atau non-array.int dis(char (*a)[1])
. Kemudian, Anda melewati pointer ke array:dis(&b)
. Jika Anda bersedia menggunakan fitur C yang tidak ada di C ++, Anda juga bisa mengatakan hal-hal sepertivoid foo(int data[static 256])
danint bar(double matrix[*][*])
, tetapi itu adalah kaleng cacing lainnya.Jawaban:
Ini adalah kekhasan dari sintaks untuk meneruskan array ke fungsi.
Sebenarnya tidak mungkin untuk melewatkan array di C. Jika Anda menulis sintaks yang terlihat seperti harus melewati array, yang sebenarnya terjadi adalah pointer ke elemen pertama array dilewatkan.
Karena pointer tidak menyertakan informasi panjang, konten Anda
[]
dalam daftar parameter formal fungsi sebenarnya diabaikan.Keputusan untuk mengizinkan sintaks ini dibuat pada tahun 1970-an dan telah menyebabkan banyak kebingungan sejak ...
sumber
void foo(int (*somearray)[20])
sintaks. dalam hal ini 20 diberlakukan di situs penelepon.[]
tidak diabaikan dalam array multidimensi seperti yang ditunjukkan pada jawaban pat. Jadi termasuk sintaks array diperlukan. Selain itu, tidak ada yang menghentikan kompiler mengeluarkan peringatan bahkan pada array satu dimensi.void foo(int (*args)[20]);
Juga, secara tegas C tidak memiliki array multi-dimensi; tetapi memiliki array yang elemen-elemennya dapat berupa array lain. Ini tidak mengubah apa pun.Panjang dimensi pertama diabaikan, tetapi panjang dimensi tambahan diperlukan untuk memungkinkan kompilator menghitung offset dengan benar. Dalam contoh berikut,
foo
fungsi dilewatkan pointer ke array dua dimensi.Ukuran dimensi pertama
[10]
diabaikan; kompiler tidak akan mencegah Anda mengindeks akhir (perhatikan bahwa formal menginginkan 10 elemen, tetapi sebenarnya hanya menyediakan 2). Namun, ukuran dimensi kedua[20]
digunakan untuk menentukan langkah setiap baris, dan di sini, formal harus cocok dengan yang sebenarnya. Sekali lagi, kompiler tidak akan mencegah Anda mengindeks dari akhir dimensi kedua.Byte offset dari basis array ke elemen
args[row][col]
ditentukan oleh:Perhatikan bahwa jika
col >= 20
, maka Anda benar-benar akan mengindeks ke baris berikutnya (atau dari akhir seluruh array).sizeof(args[0])
, mengembalikan80
mesin saya di manasizeof(int) == 4
. Namun, jika saya mencoba untuk mengambilsizeof(args)
, saya mendapatkan peringatan kompiler berikut:Di sini, kompiler memperingatkan bahwa ia hanya akan memberikan ukuran pointer ke mana array telah membusuk, bukan ukuran array itu sendiri.
sumber
args
. Dalam hal ini, elemen pertama dari args adalah "array of 20 ints". Pointer termasuk informasi jenis; apa yang dilewatkan adalah "pointer ke array 20 int".int (*)[20]
tipenya; + msgstr "arahkan ke array 20 int".Masalahnya dan bagaimana cara mengatasinya di C ++
Masalahnya telah dijelaskan secara luas oleh Pat dan Matt . Compiler pada dasarnya mengabaikan dimensi pertama ukuran array secara efektif mengabaikan ukuran argumen yang diteruskan.
Di C ++, di sisi lain, Anda dapat dengan mudah mengatasi batasan ini dengan dua cara:
std::array
(sejak C ++ 11)Referensi
Jika fungsi Anda hanya mencoba membaca atau memodifikasi array yang ada (tidak menyalinnya), Anda dapat dengan mudah menggunakan referensi.
Misalnya, mari kita asumsikan Anda ingin memiliki fungsi yang me-reset array dari sepuluh
int
pengaturan setiap elemen0
. Anda dapat dengan mudah melakukannya dengan menggunakan tanda tangan fungsi berikut:Tidak hanya ini akan berfungsi dengan baik , tetapi juga akan memperkuat dimensi array .
Anda juga dapat menggunakan templat untuk membuat kode di atas umum :
Dan akhirnya Anda bisa memanfaatkan
const
kebenaran. Mari kita pertimbangkan fungsi yang mencetak array 10 elemen:Dengan menerapkan
const
kualifikasi kami mencegah kemungkinan modifikasi .Kelas perpustakaan standar untuk array
Jika Anda menganggap sintaks di atas jelek dan tidak perlu, seperti yang saya lakukan, kita bisa melemparkannya ke dalam kaleng dan menggunakannya
std::array
sebagai gantinya (karena C ++ 11).Berikut kode yang telah di-refactored:
Luar biasa bukan? Belum lagi bahwa trik kode generik yang saya ajarkan sebelumnya, masih berfungsi:
Bukan hanya itu, tetapi Anda mendapatkan salinan dan memindahkan semantik secara gratis. :)
Jadi tunggu apa lagi? Pergi gunakan
std::array
.sumber
Ini adalah fitur menyenangkan dari C yang memungkinkan Anda untuk secara efektif menembak diri sendiri jika Anda ingin.
Saya pikir alasannya adalah bahwa C hanya satu langkah di atas bahasa assembly. Pengecekan ukuran dan fitur keselamatan serupa telah dihapus untuk memungkinkan kinerja puncak, yang bukan merupakan hal yang buruk jika programmer sedang sangat rajin.
Juga, menetapkan ukuran ke argumen fungsi memiliki keuntungan bahwa ketika fungsi tersebut digunakan oleh programmer lain, ada kemungkinan mereka akan melihat batasan ukuran. Hanya menggunakan pointer tidak menyampaikan informasi itu ke programmer berikutnya.
sumber
Pertama, C tidak pernah memeriksa batas array. Tidak masalah jika parameternya lokal, global, statis, apa pun. Memeriksa batas array berarti lebih banyak pemrosesan, dan C seharusnya sangat efisien, sehingga pemeriksaan batas array dilakukan oleh programmer ketika dibutuhkan.
Kedua, ada trik yang memungkinkan untuk melewatkan nilai array ke suatu fungsi. Dimungkinkan juga untuk mengembalikan array nilai dari suatu fungsi. Anda hanya perlu membuat tipe data baru menggunakan struct. Sebagai contoh:
Anda harus mengakses elemen-elemen seperti ini: foo.a [1]. Ekstra ".a" mungkin terlihat aneh, tetapi trik ini menambahkan fungsionalitas hebat ke bahasa C.
sumber
Untuk memberi tahu kompiler bahwa myArray menunjuk ke array setidaknya 10 int:
Kompiler yang baik harus memberi Anda peringatan jika Anda mengakses myArray [10]. Tanpa kata kunci "statis", 10 tidak akan berarti apa-apa.
sumber
[static]
memungkinkan kompiler untuk memperingatkan jika Anda memanggilbar
denganint[5]
. Itu tidak menentukan apa yang dapat Anda akses di dalambar
. Tanggung jawab sepenuhnya berada di sisi penelepon.error: expected primary-expression before 'static'
tidak pernah melihat sintaks ini. ini tidak mungkin standar C atau C ++.Ini adalah "fitur" terkenal dari C, diteruskan ke C ++ karena C ++ seharusnya mengkompilasi kode C dengan benar.
Masalah muncul dari beberapa aspek:
Bisa dikatakan array tidak benar-benar didukung dalam C (ini tidak benar, seperti yang saya katakan sebelumnya, tetapi ini adalah perkiraan yang baik); sebuah array benar-benar diperlakukan sebagai pointer ke blok data dan diakses menggunakan pointer aritmatika. Karena C TIDAK memiliki bentuk RTTI, Anda harus mendeklarasikan ukuran elemen array dalam prototipe fungsi (untuk mendukung aritmatika pointer). Ini bahkan "lebih benar" untuk array multidimensi.
Pokoknya semua di atas tidak benar lagi: p
Paling modern C / C ++ compiler melakukan batas dukungan memeriksa, tapi standar membutuhkan untuk menjadi off secara default (untuk kompatibilitas). Versi terbaru gcc, misalnya, melakukan pengecekan rentang waktu-kompilasi dengan "-O3 -Wall -Wextra" dan batas waktu run penuh memeriksa dengan "-fbounds-checking".
sumber
struct MyStruct s = { .field1 = 1, .field2 = 2 };
) untuk menginisialisasi struct, karena ini merupakan cara yang jauh lebih jelas untuk menginisialisasi struct. Akibatnya, sebagian besar kode C saat ini akan ditolak oleh kompiler C ++ standar, karena sebagian besar kode C akan menginisialisasi struct.C tidak hanya akan mengubah parameter tipe
int[5]
menjadi*int
; diberikan deklarasitypedef int intArray5[5];
, itu akan mengubah parameter tipeintArray5
untuk*int
juga. Ada beberapa situasi di mana perilaku ini, meskipun aneh, berguna (terutama dengan hal-hal seperti yangva_list
didefinisikan dalamstdargs.h
, yang didefinisikan oleh beberapa implementasi sebagai array). Akan tidak masuk akal untuk mengizinkan sebagai parameter tipe yang didefinisikanint[5]
(mengabaikan dimensi) tetapi tidak memungkinkanint[5]
untuk ditentukan secara langsung.Saya menemukan penanganan parameter parameter C menjadi tidak masuk akal, tetapi ini merupakan konsekuensi dari upaya untuk mengambil bahasa ad-hoc, sebagian besar yang tidak terlalu terdefinisi dengan baik atau dipikirkan, dan mencoba untuk datang dengan perilaku spesifikasi yang konsisten dengan apa yang dilakukan oleh implementasi yang ada untuk program yang ada. Banyak keanehan C masuk akal bila dilihat dalam cahaya itu, terutama jika orang menganggap bahwa ketika banyak dari mereka ditemukan, sebagian besar bahasa yang kita kenal sekarang belum ada. Dari apa yang saya mengerti, di pendahulunya ke C, disebut BCPL, kompiler tidak benar-benar melacak tipe variabel dengan sangat baik. Deklarasi
int arr[5];
setara denganint anonymousAllocation[5],*arr = anonymousAllocation;
; setelah alokasi disisihkan. kompiler tidak tahu atau tidak peduli apakaharr
adalah pointer atau array. Ketika diakses sebagai salah satu , itu akan dianggap sebagai penunjuk terlepas dari bagaimana itu dinyatakan.arr[x]
atau*arr
sumber
Satu hal yang belum dijawab adalah pertanyaan sebenarnya.
Jawaban yang sudah diberikan menjelaskan bahwa array tidak dapat diteruskan oleh nilai ke fungsi dalam C atau C ++. Mereka juga menjelaskan bahwa suatu parameter yang dinyatakan sebagai
int[]
diperlakukan seolah-olah memiliki tipeint *
, dan bahwa variabel tipeint[]
dapat diteruskan ke fungsi tersebut.Tetapi mereka tidak menjelaskan mengapa tidak pernah dibuat kesalahan untuk secara eksplisit memberikan panjang array.
Mengapa kesalahan terakhir ini tidak terjadi?
Alasan untuk itu adalah bahwa hal itu menyebabkan masalah dengan typedef.
Jika kesalahan menentukan panjang array dalam parameter fungsi, Anda tidak akan dapat menggunakan
myarray
nama dalam parameter fungsi. Dan karena beberapa implementasi menggunakan tipe array untuk tipe pustaka standar sepertiva_list
, dan semua implementasi diperlukan untuk membuatjmp_buf
tipe array, akan sangat bermasalah jika tidak ada cara standar untuk mendeklarasikan parameter fungsi menggunakan nama-nama tersebut: tanpa kemampuan itu, bisa ada bukan menjadi implementasi portabel fungsi sepertivprintf
.sumber
Memungkinkan kompiler dapat memeriksa apakah ukuran array yang dilewati sama dengan yang diharapkan. Compiler dapat memperingatkan masalah jika bukan itu masalahnya.
sumber