Periksa Chromium Keenam, Kata Penutup

unicorn yang parah

Pada awal 2018 blog kami dilengkapi dengan serangkaian artikel tentang pemeriksaan keenam kode sumber proyek Chromium. Seri ini mencakup 8 artikel tentang kesalahan dan rekomendasi untuk pencegahannya. Dua artikel memicu diskusi panas, dan saya masih kadang-kadang mendapat komentar melalui surat tentang topik yang dibahas di dalamnya. Mungkin, saya harus memberikan penjelasan tambahan dan seperti yang mereka katakan, luruskan.

Setahun telah berlalu sejak menulis serangkaian artikel tentang pemeriksaan rutin kode sumber proyek Chromium:

  1. Chromium: Pemeriksaan Proyek Keenam dan 250 Bug
  2. Chromium Bagus dan Canggung Memset
  3. istirahat dan gagal
  4. Chromium: kebocoran memori
  5. Chromium: Typos
  6. Chromium: Penggunaan Data Tidak Dipercaya
  7. Mengapa penting untuk memeriksa apa fungsi malloc dikembalikan
  8. Chromium: Kesalahan Lainnya

Artikel yang ditujukan untuk memset dan malloc telah menyebabkan dan terus menyebabkan perdebatan, yang menurut saya aneh. Rupanya, ada beberapa kebingungan karena fakta bahwa saya kurang akurat ketika memverbalisasi pikiran saya. Saya memutuskan untuk kembali ke artikel-artikel itu dan membuat beberapa klarifikasi.

memset


Mari kita mulai dengan artikel tentang memset , karena di sini semuanya sederhana. Beberapa argumen muncul tentang cara terbaik untuk menginisialisasi struktur. Cukup banyak programmer menulis bahwa akan lebih baik untuk memberikan rekomendasi untuk tidak menulis:

HDHITTESTINFO hhti = {}; 

selain menulis dengan cara berikut:

 HDHITTESTINFO hhti = { 0 }; 

Alasan:

  1. Konstruksi {0} lebih mudah diperhatikan ketika membaca kode, daripada {}.
  2. Konstruksi {0} lebih mudah dimengerti secara intuitif, daripada {}. Yang berarti, 0 menunjukkan bahwa struktur diisi dengan nol.

Oleh karena itu, pembaca menyarankan saya untuk mengubah contoh inisialisasi ini dalam artikel. Saya tidak setuju dengan argumen dan tidak berencana untuk mengedit artikel. Sekarang saya akan menjelaskan pendapat saya dan memberikan beberapa alasan.

Adapun visibilitas, saya pikir, ini masalah selera dan kebiasaan. Saya tidak berpikir bahwa keberadaan 0 di dalam tanda kurung secara fundamental mengubah situasi.

Adapun argumen kedua, saya benar-benar tidak setuju dengan itu. Catatan jenis {0} memberikan alasan untuk salah memahami kode. Misalnya, Anda dapat menganggap bahwa jika Anda mengganti 0 dengan 1, semua bidang akan diinisialisasi dengan yang. Karena itu, gaya penulisan seperti itu lebih cenderung berbahaya daripada membantu.

Penganalisis PVS-Studio bahkan memiliki V1009 diagnostik terkait, deskripsi yang dikutip di bawah ini.

V1009. Periksa inisialisasi array. Hanya elemen pertama yang diinisialisasi secara eksplisit.

Analisator telah mendeteksi kemungkinan kesalahan yang terkait dengan fakta bahwa ketika mendeklarasikan array nilai hanya ditentukan untuk satu elemen. Dengan demikian, elemen yang tersisa akan diinisialisasi secara implisit oleh nol atau dengan konstruktor default.

Mari kita perhatikan contoh kode yang mencurigakan:

 int arr[3] = {1}; 

Mungkin programmer yang diharapkan daripada arr akan seluruhnya terdiri dari yang, tetapi tidak. Array akan terdiri dari nilai 1, 0, 0.

Kode yang benar:

 int arr[3] = {1, 1, 1}; 

Kebingungan tersebut dapat terjadi karena kesamaan dengan konstruksi arr = {0} , yang menginisialisasi seluruh array dengan nol.

Jika konstruksi semacam itu secara aktif digunakan dalam proyek Anda, Anda dapat menonaktifkan diagnostik ini.

Kami juga menyarankan untuk tidak mengabaikan kejelasan kode Anda.

Misalnya, kode untuk pengkodean nilai warna dicatat sebagai berikut:

 int White[3] = { 0xff, 0xff, 0xff }; int Black[3] = { 0x00 }; int Green[3] = { 0x00, 0xff }; 

Berkat inisialisasi implisit, semua warna ditentukan dengan benar, tetapi lebih baik menulis ulang kode lebih jelas:

 int White[3] = { 0xff, 0xff, 0xff }; int Black[3] = { 0x00, 0x00, 0x00 }; int Green[3] = { 0x00, 0xff, 0x00 }; 

malloc


Sebelum membaca lebih lanjut, harap ingat isi artikel " Mengapa penting untuk memeriksa apa fungsi malloc dikembalikan ". Artikel ini menimbulkan banyak perdebatan dan kritik. Inilah beberapa diskusi: reddit.com/r/cpp , reddit.com/r/C_Programming , habr.com (en). Kadang-kadang pembaca masih mengirimi saya email tentang artikel ini.

Artikel ini dikritik oleh pembaca karena hal-hal berikut:

1. Jika malloc mengembalikan NULL , maka lebih baik untuk segera menghentikan program, daripada menulis sekelompok if -s dan mencoba untuk entah bagaimana menangani memori, karena itu eksekusi program seringkali tidak mungkin.

Saya belum mendorong untuk berjuang sampai akhir dengan konsekuensi kebocoran memori, dengan melewati kesalahan yang semakin tinggi. Jika diizinkan untuk aplikasi Anda untuk menghentikan pekerjaannya tanpa peringatan, maka biarkanlah demikian. Untuk keperluan ini bahkan satu cek saja setelah malloc atau menggunakan xmalloc sudah cukup (lihat poin selanjutnya).

Saya keberatan dan memperingatkan tentang kurangnya cek karena program terus bekerja seolah-olah tidak ada yang terjadi. Ini kasus yang sama sekali berbeda. Ini berbahaya, karena mengarah pada perilaku yang tidak terdefinisi, korupsi data, dan sebagainya.

2. Tidak ada deskripsi solusi yang terletak pada penulisan fungsi wrapper untuk mengalokasikan memori dengan cek yang mengikutinya atau menggunakan fungsi yang sudah ada, seperti xmalloc .

Setuju, saya melewatkan poin ini. Saat menulis artikel saya hanya tidak memikirkan cara untuk memperbaiki situasi. Lebih penting bagi saya untuk menyampaikan kepada pembaca tentang bahaya ketidakhadiran cek. Bagaimana cara memperbaiki kesalahan adalah pertanyaan tentang selera dan detail implementasi.

Fungsi xmalloc bukan bagian dari pustaka C standar (lihat " Apa perbedaan antara xmalloc dan malloc? "). Namun, fungsi ini dapat dideklarasikan di perpustakaan lain, misalnya, di perpustakaan utils GNU ( GNU libiberty ).

Poin utama dari fungsi ini adalah bahwa program macet ketika gagal mengalokasikan memori. Implementasi fungsi ini mungkin terlihat sebagai berikut:

 void* xmalloc(size_t s) { void* p = malloc(s); if (!p) { fprintf (stderr, "fatal: out of memory (xmalloc(%zu)).\n", s); exit(EXIT_FAILURE); } return p; } 

Dengan demikian, dengan memanggil fungsi xmalloc alih-alih malloc setiap kali, Anda dapat yakin bahwa perilaku yang tidak terdefinisi tidak akan terjadi dalam program karena penggunaan pointer nol.

Sayangnya, xmalloc juga bukan obat untuk semua. Orang harus ingat bahwa penggunaan xmalloc tidak dapat diterima ketika menulis kode pustaka. Saya akan membicarakannya nanti.

3. Sebagian besar komentar adalah sebagai berikut: "dalam praktiknya, malloc tidak pernah mengembalikan NULL ."

Untungnya, saya bukan satu-satunya, yang mengerti bahwa ini adalah pendekatan yang salah. Saya sangat menyukai komentar ini dalam dukungan saya:

Menurut pengalaman saya membahas topik ini, saya punya perasaan bahwa ada dua sekte di Internet. Penganut yang pertama sangat percaya bahwa malloc tidak pernah mengembalikan NULL di Linux. Pendukung yang kedua dengan sepenuh hati mengklaim bahwa jika memori tidak dapat dialokasikan dalam program Anda, tidak ada yang bisa dilakukan, Anda hanya bisa crash. Tidak ada cara untuk membujuk mereka secara berlebihan. Terutama ketika kedua sekte ini bersinggungan. Anda hanya bisa menganggapnya sebagai pemberian. Dan itu bahkan tidak penting di mana sumber daya khusus diskusi berlangsung.

Saya berpikir sebentar dan memutuskan untuk mengikuti saran, jadi saya tidak akan mencoba membujuk siapa pun :). Semoga kelompok pengembang ini hanya menulis program yang tidak fatal. Jika, misalnya, beberapa data dalam game rusak, tidak ada yang penting di dalamnya.

Satu-satunya hal yang penting adalah bahwa pengembang perpustakaan, basis data tidak boleh melakukan seperti ini.

Banding ke pengembang kode dan perpustakaan yang sangat bergantung


Jika Anda mengembangkan pustaka atau kode lain yang sangat tergantung, selalu periksa nilai pointer yang dikembalikan oleh fungsi malloc / realloc dan kembalikan kode kesalahan jika memori tidak dapat dialokasikan.

Di perpustakaan, Anda tidak dapat memanggil fungsi keluar , jika alokasi memori gagal. Untuk alasan yang sama, Anda tidak dapat menggunakan xmalloc . Untuk banyak aplikasi, tidak dapat diterima hanya membatalkannya. Karena itu, misalnya, basis data dapat rusak. Seseorang dapat kehilangan data yang dievaluasi selama berjam-jam. Karena itu, program dapat dicapai untuk kerentanan "penolakan layanan", ketika, alih-alih menangani dengan benar beban kerja yang bertambah, aplikasi multithreaded berakhir begitu saja.

Tidak dapat diasumsikan, dengan cara apa dan dalam proyek apa perpustakaan akan digunakan. Oleh karena itu, harus diasumsikan bahwa aplikasi dapat menyelesaikan tugas yang sangat kritis. Itu sebabnya hanya membunuhnya dengan memanggil keluar tidak baik. Kemungkinan besar, program semacam itu ditulis dengan mempertimbangkan kemungkinan kekurangan memori dan dapat melakukan sesuatu dalam kasus ini. Misalnya, sistem CAD tidak dapat mengalokasikan buffer memori yang sesuai yang akan cukup untuk operasi reguler karena fragmentasi memori yang kuat. Dalam hal ini, itu bukan alasan untuk menghancurkan dalam mode darurat dengan kehilangan data. Program ini dapat memberikan kesempatan untuk menyelamatkan proyek dan memulai kembali dengan sendirinya secara normal.

Dalam keadaan apa pun, mustahil untuk mengandalkan malloc yang akan selalu dapat mengalokasikan memori. Tidak diketahui pada platform apa dan bagaimana perpustakaan akan digunakan. Jika situasi memori rendah pada satu platform eksotis, itu bisa menjadi situasi yang cukup umum di platform lain.

Kami tidak dapat berharap bahwa jika malloc mengembalikan NULL , maka program akan macet. Apa pun bisa terjadi. Seperti yang saya jelaskan di artikel , program mungkin menulis data bukan dengan alamat nol. Akibatnya, beberapa data mungkin rusak, yang mengarah pada konsekuensi yang tidak terduga. Bahkan memset itu berbahaya. Jika padding dengan data berjalan dalam urutan terbalik, pertama-tama beberapa data rusak, dan kemudian program akan macet. Tetapi kecelakaan itu mungkin terjadi terlambat. Jika data tercemar digunakan dalam utas paralel saat fungsi memset berfungsi, konsekuensinya bisa fatal. Anda bisa mendapatkan transaksi yang rusak dalam database atau mengirim perintah ke penghapusan file "tidak perlu". Apa pun memiliki peluang untuk terjadi. Saya sarankan pembaca untuk bermimpi, apa yang mungkin terjadi karena penggunaan sampah dalam memori.

Dengan demikian, perpustakaan hanya memiliki satu cara yang benar untuk bekerja dengan fungsi malloc . Anda perlu SEGERA memeriksa bahwa fungsi kembali, dan jika itu NULL, maka kembalikan status kesalahan.

Tautan tambahan


  1. Penanganan OOM
  2. Bersenang-senang dengan pointer NULL: bagian 1 , bagian 2
  3. Yang Harus Diketahui Setiap Pemrogram C Tentang Perilaku Tidak Terdefinisi: bagian 1 , bagian 2 , bagian 3

Source: https://habr.com/ru/post/id438756/


All Articles