Silakan lihat kode berikut. Mencoba untuk melewatkan array sebagai char**
ke fungsi:
#include <stdio.h>
#include <stdlib.h>
static void printchar(char **x)
{
printf("Test: %c\n", (*x)[0]);
}
int main(int argc, char *argv[])
{
char test[256];
char *test2 = malloc(256);
test[0] = 'B';
test2[0] = 'A';
printchar(&test2); // works
printchar((char **) &test); // crashes because *x in printchar() has an invalid pointer
free(test2);
return 0;
}
Fakta bahwa saya hanya bisa mendapatkannya untuk mengkompilasi dengan secara eksplisit pengecoran &test2
untuk char**
sudah mengisyaratkan bahwa kode ini salah.
Tetap saja, saya bertanya-tanya apa sebenarnya yang salah tentang itu. Saya bisa meneruskan sebuah pointer ke sebuah pointer ke sebuah array yang dialokasikan secara dinamis tetapi saya tidak bisa melewatkan sebuah pointer ke sebuah pointer untuk sebuah array di stack. Tentu saja, saya dapat dengan mudah mengatasi masalah dengan terlebih dahulu menetapkan array ke variabel sementara, seperti:
char test[256];
char *tmp = test;
test[0] = 'B';
printchar(&tmp);
Namun, bisakah seseorang menjelaskan kepada saya mengapa itu tidak berhasil dilemparkan char[256]
ke char**
langsung?
char (*)[256]
kechar**
?char**
. Tanpa gips itu, ia tidak bisa dikompilasi.test
adalah array, bukan pointer, dan&test
merupakan pointer ke array. Ini bukan pointer ke pointer.Anda mungkin telah diberi tahu bahwa array adalah pointer, tetapi ini tidak benar. Nama array adalah nama seluruh objek — semua elemen. Ini bukan penunjuk ke elemen pertama. Dalam kebanyakan ekspresi, array secara otomatis dikonversi ke pointer ke elemen pertamanya. Itu adalah kenyamanan yang sering bermanfaat. Namun ada tiga pengecualian untuk aturan ini:
sizeof
.&
.Dalam
&test
, array adalah operan&
, jadi konversi otomatis tidak terjadi. Hasilnya&test
adalah pointer ke array 256char
, yang bertipechar (*)[256]
, bukanchar **
.Untuk mendapatkan pointer ke pointer
char
daritest
, Anda harus terlebih dahulu membuat pointer kechar
. Sebagai contoh:Cara lain untuk memikirkan hal ini adalah dengan menyadari bahwa
test
menamai seluruh objek — seluruh array 256char
. Itu tidak menamai pointer, jadi, di&test
, tidak ada pointer yang alamatnya dapat diambil, jadi ini tidak dapat menghasilkan achar **
. Untuk membuatchar **
, Anda harus terlebih dahulu memilikichar *
.sumber
_Alignof
operator yang disebutkan di sampingsizeof
dan&
. Saya ingin tahu mengapa mereka menghapusnya ..._Alignof
hanya menerima nama jenis sebagai operan dan tidak pernah menerima array atau objek lain sebagai operan. (Saya tidak tahu mengapa; sepertinya secara sintaksis dan tata bahasa bisa sepertisizeof
, tetapi tidak.)Jenisnya
test2
adalahchar *
. Jadi, jenis&test2
akan menjadichar **
yang kompatibel dengan jenis parameterx
dariprintchar()
.Jenisnya
test
adalahchar [256]
. Jadi, jenis&test
akan menjadichar (*)[256]
yang tidak kompatibel dengan jenis parameterx
dariprintchar()
.Biarkan saya menunjukkan kepada Anda perbedaan dalam hal alamat
test
dantest2
.Keluaran:
Poin yang perlu diperhatikan di sini:
Output (alamat memori) dari
test2
dan&test2[0]
secara numerik sama dan tipenya juga sama yaituchar *
.Tetapi
test2
dan&test2
adalah alamat yang berbeda dan jenis mereka juga berbeda.Jenisnya
test2
adalahchar *
.Jenisnya
&test2
adalahchar **
.Output (alamat memori) dari
test
,&test
dan&test[0]
secara numerik sama tetapi tipenya berbeda .Jenisnya
test
adalahchar [256]
.Jenisnya
&test
adalahchar (*) [256]
.Jenisnya
&test[0]
adalahchar *
.Sebagai output menunjukkan
&test
sama dengan&test[0]
.Karenanya Anda mendapatkan kesalahan segmentasi.
sumber
Anda tidak dapat mengakses pointer ke pointer karena
&test
bukan pointer — ini adalah array.Jika Anda mengambil alamat array, melemparkan array dan alamat array ke
(void *)
, dan membandingkannya, mereka akan (membatasi kemungkinan pointer pedantry) setara.Apa yang sebenarnya Anda lakukan mirip dengan ini (sekali lagi, cegah alias ketat):
yang jelas-jelas salah.
sumber
Kode Anda mengharapkan argumen
x
dariprintchar
titik ke memori yang berisi(char *)
.Dalam panggilan pertama, itu menunjuk ke penyimpanan yang digunakan untuk
test2
dan dengan demikian memang nilai yang menunjuk ke(char *)
, yang terakhir menunjuk ke memori yang dialokasikan.Dalam panggilan kedua, bagaimanapun, tidak ada tempat di mana
(char *)
nilai tersebut dapat disimpan dan oleh karena itu tidak mungkin untuk menunjuk ke memori tersebut. Para pemain yang(char **)
Anda tambahkan akan menghapus kesalahan kompilasi (tentang konversi(char *)
ke(char **)
) tetapi itu tidak akan membuat penyimpanan muncul dari udara tipis untuk mengandung(char *)
inisialisasi yang mengarah ke karakter pertama pengujian. Pointer casting dalam C tidak mengubah nilai aktual dari pointer.Untuk mendapatkan yang Anda inginkan, Anda harus melakukannya secara eksplisit:
Saya menganggap contoh Anda adalah penyulingan dari sepotong kode yang jauh lebih besar; sebagai contoh, mungkin Anda ingin
printchar
menambah(char *)
nilai yangx
ditunjukkan nilai yang diteruskan sehingga pada panggilan berikutnya karakter berikutnya dicetak. Jika bukan itu masalahnya, mengapa Anda tidak hanya(char *)
menunjuk ke karakter yang akan dicetak, atau bahkan hanya meneruskan karakter itu sendiri?sumber
char **
. Variabel array / benda sederhana yang array, dengan alamat yang implisit, di mana saja tidak disimpan. Tidak ada tingkat tipuan tambahan untuk mengaksesnya, tidak seperti dengan variabel penunjuk yang menunjuk ke penyimpanan lain.Rupanya, mengambil alamat
test
sama dengan mengambil alamattest[0]
:Kompilasi dan jalankan:
Jadi penyebab utama kesalahan segmentasi adalah bahwa program ini akan mencoba untuk meringkas alamat absolut
0x42
(juga dikenal sebagai'B'
), yang program Anda tidak memiliki izin untuk membaca.Meskipun dengan kompiler / mesin yang berbeda, alamatnya akan berbeda: Coba online! , tetapi Anda masih akan mendapatkan ini, karena beberapa alasan:
Tetapi alamat yang menyebabkan kesalahan segmentasi mungkin sangat berbeda:
sumber
test
tidak sama dengan mengambil alamattest[0]
. Yang pertama memiliki tipechar (*)[256]
, dan yang kedua memiliki tipechar *
. Mereka tidak kompatibel, dan standar C memungkinkan mereka untuk memiliki representasi yang berbeda.%p
, itu harus dikonversi kevoid *
(lagi untuk alasan kompatibilitas dan representasi).printchar(&test);
mungkin macet untuk Anda, tetapi perilaku tidak ditentukan oleh standar C, dan orang-orang dapat mengamati perilaku lain dalam keadaan lain.&test == &test[0]
melanggar kendala dalam C 2018 6.5.9 2 karena tipe tidak kompatibel. Standar C membutuhkan implementasi untuk mendiagnosis pelanggaran ini, dan perilaku yang dihasilkan tidak ditentukan oleh standar C. Itu berarti kompiler Anda mungkin menghasilkan kode yang menilai mereka sama, tetapi kompiler lain mungkin tidak.Representasi
char [256]
tergantung pada implementasi. Itu tidak harus sama denganchar *
.Casting
&test
tipechar (*)[256]
untukchar **
menghasilkan perilaku yang tidak terdefinisi.Dengan beberapa kompiler, mungkin melakukan apa yang Anda harapkan, dan yang lain tidak.
EDIT:
Setelah pengujian dengan gcc 9.2.1, tampaknya
printchar((char**)&test)
lolostest
sebagai nilai yang diberikanchar**
. Seolah-olah instruksi ituprintchar((char**)test)
. Dalamprintchar
fungsinya,x
adalah pointer ke char pertama dari tes array, bukan double pointer ke karakter pertama. Hasil de-referensi gandax
dalam kesalahan segmentasi karena 8 byte pertama dari array tidak sesuai dengan alamat yang valid.Saya mendapatkan perilaku dan hasil yang sama persis ketika mengkompilasi program dengan dentang 9.0.0-2.
Ini dapat dianggap sebagai bug penyusun, atau hasil dari perilaku tidak terdefinisi yang hasilnya mungkin khusus untuk penyusun.
Perilaku tak terduga lainnya adalah kode itu
Outputnya adalah
Perilaku anehnya adalah itu
x
dan*x
memiliki nilai yang sama.Ini adalah kompiler. Saya ragu bahwa ini didefinisikan oleh bahasa.
sumber
char (*)[256]
bergantung pada implementasi? Representasichar [256]
tidak relevan dalam pertanyaan ini — itu hanya sekumpulan bit. Tapi, bahkan jika Anda maksud representasi pointer ke array berbeda dari representasi pointer ke pointer, itu juga meleset. Bahkan jika mereka memiliki representasi yang sama, kode OP tidak akan berfungsi, karena penunjuk ke penunjuk dapat ditinjau dua kali, seperti yang dilakukanprintchar
, tetapi penunjuk ke array tidak dapat, terlepas dari penyajiannya.char (*)[256]
kechar **
diterima oleh kompiler, tetapi tidak menghasilkan hasil yang diharapkan karena achar [256]
tidak sama dengan achar *
. Saya berasumsi, pengkodeannya berbeda, jika tidak akan menghasilkan hasil yang diharapkan.char **
, perilaku tidak terdefinisi, dan bahwa, jika hasilnya dikonversi kembali kechar (*)[256]
, itu sebanding dengan pointer asli. Dengan "hasil yang diharapkan", Anda dapat berarti bahwa, jika(char **) &test
dikonversi lebih jauh ke achar *
, hasilnya sama dengan&test[0]
. Itu bukan hasil yang tidak mungkin dalam implementasi yang menggunakan ruang alamat datar, tapi itu bukan masalah representasi.char **
ischar *
), maka perilaku tersebut tidak terdefinisi. Kalau tidak, konversi didefinisikan, meskipun nilainya hanya sebagian didefinisikan, per komentar saya di atas.char (*x)[256]
tidak sama denganchar **x
. Alasanx
dan*x
mencetak nilai pointer yang sama adalah itux
hanyalah pointer ke array. Anda*x
adalah array , dan menggunakannya dalam konteks pointer meluruh kembali ke alamat array . Tidak ada bug kompiler di sana (atau dalam hal apa(char **)&test
), hanya sedikit senam mental yang diperlukan untuk mencari tahu yang terjadi dengan jenis. (cdecl menjelaskannya sebagai "menyatakan x sebagai pointer ke array 256 dari char"). Bahkan menggunakanchar*
untuk mengakses objek-representasichar**
bukan UB; bisa alias apa saja.