Secara khusus, apa yang berbahaya tentang casting hasil malloc?

87

Sekarang sebelum orang mulai menandai ini sebagai dup, saya telah membaca semua yang berikut, tidak ada yang memberikan jawaban yang saya cari:

  1. C FAQ: Apa yang salah dengan nilai pengembalian malloc?
  2. SO: Haruskah saya secara eksplisit menggunakan nilai kembalian malloc ()?
  3. SO: Pointer-cast yang tidak perlu di C
  4. SO: Apakah saya memberikan hasil malloc?

C FAQ dan banyak jawaban atas pertanyaan di atas mengutip kesalahan misterius yang mallocdapat disembunyikan oleh nilai pengembalian casting ; namun, tidak satupun dari mereka memberikan contoh spesifik dari kesalahan seperti itu dalam praktiknya. Sekarang perhatikan bahwa saya mengatakan kesalahan , bukan peringatan .

Sekarang diberi kode berikut:

Mengompilasi kode di atas dengan gcc 4.2, dengan dan tanpa cast memberikan peringatan yang sama, dan program dijalankan dengan benar dan memberikan hasil yang sama pada kedua kasus.

Jadi adakah yang bisa memberikan contoh kode spesifik dari kompilasi atau runtime error yang dapat terjadi karena mallocnilai kembalian casting , atau apakah ini hanya legenda urban?

Sunting Saya menemukan dua argumen yang ditulis dengan baik mengenai masalah ini:

  1. Mendukung Casting: CERT Advisory: Segera mentransmisikan hasil panggilan fungsi alokasi memori ke pointer ke tipe yang dialokasikan
  2. Against Casting (kesalahan 404 per 2012-02-14: gunakan salinan Internet Archive Wayback Machine dari 2010-01-27. {2016-03-18: "Halaman tidak dapat dirayapi atau ditampilkan karena robots.txt."})
Robert S. Barnes
sumber
6
casting voidpointer memungkinkan untuk mengkompilasi kode sebagai C ++; beberapa orang mengatakan itu fitur, menurut saya ini bug;)
Christoph
1
juga, baca komentar di tautan pertama Anda karena ini menjelaskan apa yang harus Anda lakukan daripada melakukan casting: securecoding.cert.org/confluence/display/seccode/…
Christoph
3
Saya akan menerima saran CERT untuk memasukkan para pemerannya. Juga, saya tidak akan lupa untuk memasukkan stdlib.h, selamanya. :)
Abhinav
1
Berikut adalah contoh SO dari kesalahan runtime kompilasi karena mallocnilai kembalian casting: casting ke int*pada arch 64-bit.
John_West
1
pertanyaan ini Ctidak diberi tag C++(keduanya adalah bahasa yang berbeda) Jadi diskusi apa pun (seperti pada beberapa jawaban) tidak relevan dengan pertanyaan ini.
pengguna3629249

Jawaban:

66

Anda tidak akan mendapatkan kesalahan kompilator , tetapi peringatan kompilator . Seperti yang dikatakan sumber yang Anda kutip (terutama yang pertama ), Anda bisa mendapatkan error runtime yang tidak dapat diprediksi saat menggunakan cast tanpa menyertakannyastdlib.h .

Jadi kesalahan di pihak Anda bukanlah pada pemerannya, melainkan lupa untuk dimasukkan stdlib.h. Penyusun dapat menganggap bahwa itu mallocadalah fungsi yang kembali int, oleh karena itu mengonversi void*penunjuk yang sebenarnya dikembalikan oleh mallocke intdan kemudian ke jenis penunjuk Anda karena cast eksplisit. Pada beberapa platform, intdan pointer dapat mengambil jumlah byte yang berbeda, sehingga jenis konversi dapat menyebabkan kerusakan data.

Untungnya, kompiler modern memberikan peringatan yang mengarah ke kesalahan Anda yang sebenarnya. Lihat gcckeluaran yang Anda berikan: Ini memperingatkan Anda bahwa deklarasi implisit ( int malloc(int)) tidak kompatibel dengan bawaan malloc. Jadi gccsepertinya tahu mallocbahkan tanpa stdlib.h.

Meninggalkan pemeran untuk mencegah kesalahan ini sebagian besar merupakan alasan yang sama seperti menulis

dari pada

karena yang terakhir dapat menyebabkan bug yang serius jika ada yang bingung =dan ==, sedangkan yang pertama akan menyebabkan kesalahan kompilasi. Saya pribadi lebih suka gaya yang terakhir karena lebih mencerminkan niat saya dan saya tidak cenderung melakukan kesalahan ini.

Hal yang sama berlaku untuk casting nilai yang dikembalikan oleh malloc: Saya lebih suka eksplisit dalam pemrograman dan saya biasanya memeriksa ulang untuk memasukkan file header untuk semua fungsi yang saya gunakan.

Ferdinand Beyer
sumber
2
Tampaknya karena compiler memperingatkan tentang deklarasi implisit yang tidak kompatibel, maka ini bukan masalah selama Anda memperhatikan peringatan compiler Anda.
Robert S. Barnes
4
@Robert: ya, dengan asumsi tertentu tentang kompilator. Ketika orang memberi nasihat tentang cara terbaik untuk menulis C secara umum , mereka tidak dapat berasumsi bahwa orang yang menerima nasihat itu menggunakan gcc versi terbaru.
Steve Jessop
4
Oh, dan jawaban untuk pertanyaan kedua adalah bahwa pemanggil berisi kode untuk mengambil nilai yang dikembalikan (yang dianggapnya int), dan mengubahnya menjadi T *. Callee hanya menulis nilai return (sebagai void *) dan return. Jadi tergantung pada konvensi pemanggilan: int return dan void * return mungkin atau mungkin tidak berada di "tempat yang sama" (register atau slot stack); int dan void * mungkin atau mungkin tidak berukuran sama; mengkonversi antara keduanya mungkin atau mungkin tidak menjadi no-op. Jadi itu mungkin "hanya berfungsi", atau nilainya bisa rusak (beberapa bit hilang, mungkin), atau penelepon dapat mengambil nilai yang salah sepenuhnya.
Steve Jessop
1
@ RobertS.Barnes terlambat ke pesta, tetapi: Nilai yang dikembalikan umumnya bukan bagian dari tanda tangan fungsi, bahkan di C ++. Linker hanya menghasilkan lompatan ke simbol, itu saja.
Peter - Kembalikan Monica
3
Anda bisa mendapatkan error runtime yang tidak dapat diprediksi saat menggunakan cast tanpa menyertakan stdlib.h . Itu benar, tetapi tidak menyertakan stdlib.hsudah merupakan kesalahan dengan sendirinya, meskipun Anda hanya mendapatkan peringatan "deklarasi implisit".
Jabberwocky
45

Salah satu argumen tingkat tinggi yang baik terhadap casting hasil mallocsering tidak disebutkan, meskipun, menurut pendapat saya, itu lebih penting daripada masalah tingkat rendah yang terkenal (seperti memotong penunjuk ketika deklarasi hilang).

Praktik pemrograman yang baik adalah menulis kode, yang sebisa mungkin tidak tergantung tipe. Artinya, secara khusus, nama tipe harus disebutkan dalam kode sesedikit mungkin atau sebaiknya tidak disebutkan sama sekali. Ini berlaku untuk cast (hindari cast yang tidak perlu), jenis sebagai argumen sizeof(hindari menggunakan nama jenis dalam sizeof) dan, umumnya, semua referensi lain untuk nama jenis.

Nama jenis termasuk dalam deklarasi. Sebisa mungkin, nama tipe harus dibatasi pada deklarasi dan hanya pada deklarasi.

Dari sudut pandang ini, sedikit kode ini buruk

dan ini jauh lebih baik

bukan hanya karena "tidak mentransmisikan hasil dari malloc", melainkan karena ini adalah tipe-independen (atau tipe-agnositic, jika Anda lebih suka), karena secara otomatis menyesuaikan diri dengan tipe apa pun pyang dideklarasikan dengan, tanpa memerlukan intervensi apa pun dari pengguna.

Semut
sumber
Fwiw, saya pikir alasan ini kurang lebih sama dengan ini: stackoverflow.com/questions/953112/… tetapi berfokus pada jenis-independensi daripada DIY. Tentu saja yang pertama mengikuti dari yang kedua (atau sebaliknya), jadi setidaknya kadang - kadang disebutkan . :)
bersantai
5
@unwind Anda kemungkinan besar berarti KERING daripada DIY
kratenko
18

Fungsi non-prototipe diasumsikan kembali int.

Jadi Anda mentransmisikan intke sebuah pointer. Jika petunjuk lebih luas dari intpada platform Anda, ini adalah perilaku yang sangat berisiko.

Ditambah, tentu saja, beberapa orang menganggap peringatan sebagai kesalahan, yaitu kode harus dikompilasi tanpa mereka.

Secara pribadi, saya pikir fakta bahwa Anda tidak perlu mentransmisikan void *ke tipe penunjuk lain adalah fitur di C, dan menganggap kode yang tidak rusak.

beristirahat
sumber
14
Saya memiliki keyakinan bahwa kompilator tahu lebih banyak tentang bahasa tersebut daripada saya, jadi jika ia memperingatkan saya tentang sesuatu, saya perhatikan.
György Andrasek
3
Dalam banyak proyek, kode C dikompilasi sebagai C ++ di mana Anda perlu mentransmisikan void*.
laalto
nit: " secara default , fungsi non-prototipe diasumsikan kembali int." - Apakah yang Anda maksud adalah mungkin untuk mengubah tipe kembalian dari fungsi non-prototipe?
pmg
1
@ laalto - Memang, tetapi seharusnya tidak. C adalah C, bukan C ++, dan harus dikompilasi dengan kompilator C, bukan kompilator C ++. Tidak ada alasan: GCC (salah satu kompiler C terbaik di luar sana) berjalan di hampir semua platform yang bisa dibayangkan (dan juga menghasilkan kode yang sangat dioptimalkan). Apa alasan Anda mungkin harus mengkompilasi C dengan kompiler C ++, selain standar kemalasan dan longgar?
Chris Lutz
3
Contoh kode yang mungkin Anda ingin mengkompilasi baik sebagai C dan C ++: #ifdef __cplusplus \nextern "C" { \n#endif static inline uint16_t swb(uint16_t a) {return ((a << 8) | ((a >> 8) & 0xFF); } \n#ifdef __cplusplus\n } \n#endif. Sekarang, mengapa Anda ingin memanggil malloc dalam fungsi sebaris statis saya benar-benar tidak tahu, tetapi header yang berfungsi di keduanya hampir tidak pernah terdengar.
Steve Jessop
11

Jika Anda melakukan ini saat mengompilasi dalam mode 64-bit, pointer yang Anda kembalikan akan dipotong menjadi 32-bit.

EDIT: Maaf terlalu singkat. Berikut adalah contoh fragmen kode untuk tujuan diskusi.

utama()
{
   char * c = (char *) malloc (2);
   printf ("% p", c);
}

Misalkan penunjuk heap yang dikembalikan adalah sesuatu yang lebih besar dari apa yang dapat direpresentasikan dalam int, katakanlah 0xAB00000000.

Jika malloc tidak dibuat prototipe untuk mengembalikan pointer, nilai int yang dikembalikan awalnya akan berada di beberapa register dengan semua bit signifikan yang ditetapkan. Sekarang kompilator berkata, "oke, bagaimana cara mengubah dan int menjadi pointer". Itu akan menjadi salah satu ekstensi tanda atau ekstensi nol dari 32-bit orde rendah yang telah diberitahu malloc "kembali" dengan menghilangkan prototipe. Karena int ditandatangani, saya pikir konversinya akan menjadi ekstensi tanda, yang dalam hal ini akan mengubah nilai menjadi nol. Dengan nilai kembali 0xABF0000000 Anda akan mendapatkan penunjuk bukan nol yang juga akan menyenangkan saat Anda mencoba membedakannya.

Peeter Joot
sumber
1
Bisakah Anda menjelaskan secara rinci bagaimana ini akan terjadi?
Robert S. Barnes
5
Saya pikir Peeter Joot sedang mencari tahu bahwa "Secara default, fungsi non-prototipe diasumsikan kembali int" w / o termasuk stdlib.h, dan sizeof (int) adalah 32 bit sedangkan sizeof (ptr) adalah 64.
Uji
4

Aturan Perangkat Lunak yang Dapat Digunakan Kembali:

Dalam kasus penulisan fungsi inline yang menggunakan malloc (), agar dapat digunakan kembali untuk kode C ++ juga, lakukan pengecoran tipe eksplisit (mis. (Char *)); jika tidak, kompilator akan mengeluh.

Uji
sumber
mudah-mudahan, dengan penyertaan (baru-baru ini) pengoptimalan waktu tautan di gcc (lihat gcc.gnu.org/ml/gcc/2009-10/msg00060.html ), mendeklarasikan fungsi-inline di file header tidak lagi diperlukan
Christoph
kamu punya ide buruk. Tahukah Anda bahwa apa yang portabel dan lintas platform di antara kompiler / versi / arsitektur yang berbeda? ok, mungkin tidak. lalu apa artinya dapat digunakan kembali?
Uji
2
saat menulis C ++, malloc / free BUKAN metodologi yang tepat. Lebih baik gunakan baru / hapus. IE seharusnya tidak ada / nada / nol panggilan ke malloc / gratis dalam kode C ++
pengguna3629249
3
@ user3629249: Ketika menulis fungsi yang harus digunakan dari dalam baik kode C atau C ++ kode, menggunakan malloc/ freeuntuk kedua cenderung untuk menjadi lebih baik daripada mencoba untuk menggunakan mallocdi C dan newC ++, terutama jika struktur data dibagi antara C dan C ++ kode dan ada kemungkinan bahwa objek mungkin dibuat dalam kode C dan dirilis dalam kode C ++ atau sebaliknya.
supercat
3

Sebuah pointer kosong di C dapat diberikan ke pointer apapun tanpa cast eksplisit. Compiler akan memberikan peringatan tetapi dapat digunakan kembali di C ++ dengan mentransmisikan tipe malloc()ke tipe yang sesuai. Tanpa pengecoran tipe juga dapat digunakan di C , karena C tidak ada pengecekan tipe yang ketat . Tetapi C ++ secara ketat memeriksa tipe sehingga diperlukan untuk mengetikkan cast malloc()di C ++.

Yogeesh HT
sumber
Jika Anda menggunakan malloc di C ++ Anda sebaiknya memiliki alasan yang sangat bagus! ; p
semut