4 kesalahan pengaturcaraan C yang biasa - dan 5 petua untuk mengelakkannya

Hanya sedikit bahasa pengaturcaraan yang dapat menandingi C untuk kelajuan dan kekuatan tahap mesin. Kenyataan ini benar 50 tahun yang lalu, dan masih berlaku hingga kini. Namun, ada sebab mengapa pengaturcara mencipta istilah "senapang kaki" untuk menggambarkan jenis kekuatan C. Sekiranya anda tidak berhati-hati, C boleh melucutkan jari kaki anda - atau jari kaki orang lain.

Berikut adalah empat kesalahan paling biasa yang boleh anda buat dengan C, dan lima langkah yang boleh anda lakukan untuk mencegahnya.

Kesalahan C biasa: Tidak membebaskan mallocmemori (atau membebaskannya lebih dari sekali)

Ini adalah salah satu kesalahan besar dalam C, yang banyak melibatkan pengurusan memori. Memori yang dialokasikan (dilakukan dengan menggunakan malloc fungsi) tidak dilupuskan secara automatik di C. Ini adalah tugas pengaturcara untuk membuang memori tersebut ketika tidak lagi digunakan. Gagal membebaskan permintaan memori berulang, dan anda akan berakhir dengan kebocoran memori. Cuba gunakan kawasan memori yang sudah dibebaskan, dan program anda akan hancur — atau, lebih buruk lagi, akan pincang dan menjadi rentan terhadap serangan menggunakan mekanisme itu.

Perhatikan bahawa kebocoran memori hanya dapat menggambarkan keadaan di mana memori seharusnya dibebaskan, tetapi tidak. Sekiranya program terus mengalokasikan memori kerana memori sebenarnya diperlukan dan digunakan untuk bekerja, maka penggunaan memori mungkin  tidak efisien , tetapi secara tegas ia tidak bocor.

Kesalahan C biasa: Membaca larik di luar batas

Di sini kita mempunyai satu lagi kesalahan yang paling biasa dan berbahaya di C. Pembacaan yang berlalu di akhir larik dapat mengembalikan data sampah. Penulisan melepasi batas larik mungkin merosakkan keadaan program, atau merosakkannya sepenuhnya, atau, yang paling teruk, menjadi vektor serangan untuk perisian hasad.

Jadi mengapa beban memeriksa batas array diserahkan kepada pengaturcara? Dalam spesifikasi C rasmi, membaca atau menulis larik di luar batasannya adalah "tingkah laku yang tidak ditentukan," yang bermaksud spesifikasi tersebut tidak dapat mengatakan apa yang seharusnya berlaku. Penyusun bahkan tidak perlu mengadu mengenainya.

C telah lama memilih untuk memberi kuasa kepada pengaturcara walaupun dengan risiko mereka sendiri. Pembacaan atau penulisan luar batas biasanya tidak terperangkap oleh penyusun, melainkan anda secara khusus mengaktifkan pilihan penyusun untuk mencegahnya. Lebih-lebih lagi, mungkin melampaui batas larik pada waktu berjalan dengan cara yang bahkan tidak dapat dilindungi oleh pemeriksaan penyusun.

Kesalahan C biasa: Tidak memeriksa hasil dari malloc

malloc dan calloc (untuk memori pra-sifar) adalah fungsi perpustakaan C yang memperoleh memori yang dialokasikan timbunan dari sistem. Sekiranya mereka tidak dapat mengalokasikan memori, mereka menghasilkan ralat. Pada masa-masa ketika komputer mempunyai memori yang agak kecil, ada peluang yang baik untuk panggilan malloctidak berjaya.

Walaupun komputer hari ini memiliki gigabyte RAM untuk dilemparkan, masih selalu ada kemungkinan mallocgagal, terutama di bawah tekanan memori tinggi atau ketika mengalokasikan kepingan memori sekaligus. Hal ini terutama berlaku untuk program C yang "memperuntukkan slab" sekumpulan besar memori dari OS terlebih dahulu dan kemudian membaginya untuk kegunaan mereka sendiri. Sekiranya peruntukan pertama gagal kerana terlalu besar, anda mungkin dapat memerangkap penolakan itu, menurunkan peruntukan, dan menyesuaikan heuristik penggunaan memori program dengan sewajarnya. Tetapi jika peruntukan memori gagal dijumpai, keseluruhan program dapat berjalan lancar.

Kesalahan C biasa: Menggunakan void*penunjuk generik ke memori

Menggunakan  void* untuk menunjuk ke ingatan adalah kebiasaan lama - dan buruk. Petunjuk untuk ingatan perlu sentiasa char*, unsigned char*atau  uintptr_t*. Suite penyusun C moden harus disediakan uintptr_tsebagai sebahagian daripada stdint.h

Apabila dilabel dengan salah satu cara ini, jelas bahawa penunjuk merujuk pada lokasi memori di abstrak dan bukan pada beberapa jenis objek yang tidak ditentukan. Perkara ini sangat penting jika anda menjalankan matematik pointer. Dengan  uintptr_t*dan seumpamanya, elemen ukuran yang ditunjukkan, dan bagaimana ia akan digunakan, tidak jelas. Dengan void*, tidak begitu banyak.

Mengelakkan kesalahan C biasa - 5 petua

Bagaimana anda mengelakkan kesilapan yang terlalu umum ini ketika bekerja dengan memori, tatasusunan, dan petunjuk di C? Ingatlah lima petua ini. 

Struktur program C supaya pemilikan memori tetap jelas

Sekiranya anda baru memulakan aplikasi C, ada baiknya memikirkan cara memori diperuntukkan dan dikeluarkan sebagai salah satu prinsip organisasi untuk program ini. Sekiranya tidak jelas di mana peruntukan memori tertentu dibebaskan atau dalam keadaan apa, anda meminta masalah. Buat usaha tambahan untuk menjadikan pemilikan memori sejelas mungkin. Anda akan menolong diri anda (dan pemaju masa depan).

Inilah falsafah di sebalik bahasa seperti Rust. Rust menjadikan mustahil untuk menulis program yang disusun dengan baik melainkan anda menyatakan dengan jelas bagaimana memori dimiliki dan dipindahkan. C tidak mempunyai sekatan seperti itu, tetapi bijaksana untuk menerapkan filosofi itu sebagai petunjuk yang memungkinkan.

Gunakan pilihan penyusun C yang melindungi daripada masalah memori

Sebilangan besar masalah yang dijelaskan pada separuh pertama artikel ini dapat ditangani dengan menggunakan pilihan penyusun yang ketat. Edisi terbaru gcc, misalnya, menyediakan alat seperti AddressSanitizer ("ASAN") sebagai pilihan penyusunan untuk memeriksa kesalahan pengurusan memori biasa.

Berhati-hatilah, alat ini tidak dapat menangkap segalanya. Mereka pagar pengawal; mereka tidak merebut stereng jika anda keluar dari jalan raya. Juga, beberapa alat ini, seperti ASAN, mengenakan kompilasi dan kos waktu operasi, jadi harus dielakkan dalam pembuatan rilis.

Gunakan Cppcheck atau Valgrind untuk menganalisis kod C untuk kebocoran memori

Di mana penyusun sendiri kekurangan, alat lain masuk untuk mengisi jurang itu - terutamanya ketika menganalisis tingkah laku program pada waktu berjalan.

Cppcheck menjalankan analisis statik pada kod sumber C untuk mencari kesalahan biasa dalam pengurusan memori dan tingkah laku yang tidak ditentukan (antara lain).

Valgrind menyediakan cache alat untuk mengesan kesalahan memori dan utas semasa menjalankan program C. Ini jauh lebih hebat daripada menggunakan analisis waktu kompilasi, kerana anda dapat memperoleh maklumat mengenai tingkah laku program ketika ia disiarkan secara langsung. Kelemahannya adalah bahawa program berjalan pada sebahagian kecil dari kelajuan normalnya. Tetapi ini biasanya baik untuk ujian.

Alat ini bukan peluru perak dan tidak akan menangkap semuanya. Tetapi mereka berfungsi sebagai sebahagian daripada strategi pertahanan umum terhadap salah urus memori di C.

Automatik pengurusan memori C dengan pengumpul sampah

Oleh kerana kesalahan ingatan adalah sumber masalah C yang jelas, berikut adalah satu penyelesaian mudah: Jangan menguruskan memori di C secara manual. Gunakan pengutip sampah. 

Ya, ini dapat dilakukan di C. Anda boleh menggunakan sesuatu seperti pengumpul sampah Boehm-Demers-Weiser untuk menambahkan pengurusan memori automatik ke program C. Untuk beberapa program, penggunaan pengumpul Boehm bahkan dapat mempercepat. Ia bahkan dapat digunakan sebagai mekanisme pengesanan kebocoran.

Kelemahan utama pengumpul sampah Boehm ialah ia tidak dapat mengimbas atau membebaskan memori yang menggunakan lalai malloc. Ia menggunakan fungsi peruntukannya sendiri, dan hanya berfungsi pada memori yang anda peruntukkan khusus dengannya.

Jangan gunakan C apabila bahasa lain berlaku

Sebilangan orang menulis dalam huruf C kerana mereka benar-benar menikmatinya dan merasa berbuah. Namun secara keseluruhan, lebih baik menggunakan C hanya apabila anda mesti, dan kemudian hanya dengan berhati-hati, untuk beberapa keadaan di mana ia benar-benar merupakan pilihan yang ideal.

Sekiranya anda mempunyai projek di mana prestasi pelaksanaan akan dibatasi terutamanya oleh I / O atau akses cakera, menulisnya dalam C tidak mungkin menjadikannya lebih pantas dengan cara yang penting, dan mungkin hanya akan menjadikannya lebih mudah ralat dan sukar untuk menjaga. Program yang sama boleh ditulis dalam Go atau Python.

Pendekatan lain adalah menggunakan C hanya untuk bahagian aplikasi yang benar-benar berprestasi , dan bahasa yang lebih dipercayai walaupun lebih perlahan untuk bahagian lain. Sekali lagi, Python dapat digunakan untuk membungkus perpustakaan C atau kod C tersuai, menjadikannya pilihan yang baik untuk komponen yang lebih banyak boiler seperti pengendalian pilihan baris perintah.