Tes Chromium Keenam, Kata Penutup

unicorn yang ketat

Pada awal 2018, serangkaian artikel muncul di blog kami yang didedikasikan untuk verifikasi keenam kode sumber proyek Chromium. Siklus mencakup 8 artikel yang ditujukan untuk kesalahan dan rekomendasi untuk pencegahannya. Dua artikel telah menyebabkan diskusi panas, dan sejauh ini saya jarang menerima komentar tentang topik yang dibahas di dalamnya. Mungkin, beberapa penjelasan tambahan harus diberikan dan, seperti kata mereka, beri tanda titik pada i.

Setahun telah berlalu sejak penulisan serangkaian artikel yang ditujukan untuk verifikasi kode sumber proyek Chromium berikutnya:

  1. Chromium: cek proyek keenam dan 250 bug
  2. Chromium yang indah dan memset yang kikuk
  3. istirahat dan gagal
  4. Chromium: kebocoran memori
  5. Salah ketik Chromium
  6. Chromium: Menggunakan Data yang Tidak Akurat
  7. Mengapa penting untuk memeriksa apa fungsi malloc dikembalikan
  8. Chromium: kesalahan lain

Artikel tentang memset dan malloc telah menyebabkan, dan terus menyebabkan, diskusi yang terasa aneh bagi saya. Rupanya, ada beberapa kesalahpahaman karena fakta bahwa saya tidak merumuskan pikiran saya dengan jelas. Saya memutuskan untuk kembali ke artikel ini dan membuat beberapa klarifikasi.

memset


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

HDHITTESTINFO hhti = {}; 

Jadi:

 HDHITTESTINFO hhti = { 0 }; 

Argumen:

  1. Konstruk {0} lebih mudah dilihat ketika membaca kode daripada {}.
  2. Konstruk {0} lebih intuitif daripada {}. Yaitu, 0 menunjukkan bahwa struktur diisi dengan nol.

Oleh karena itu, saya ditawari untuk mengubah contoh inisialisasi ini dalam artikel. Saya tidak setuju dengan argumen dan tidak berencana untuk mengubah artikel. Sekarang saya membenarkan posisi saya.

Tentang visibilitas. Saya pikir ini masalah selera dan kebiasaan. Saya tidak berpikir bahwa keberadaan 0 di dalam kurung kurawal secara fundamental mengubah situasi.

Tetapi dengan argumen kedua saya tidak setuju sama sekali. Catatan seperti {0} memberikan alasan untuk salah mengerti kode. Misalnya, Anda dapat menghitung bahwa jika Anda mengganti 0 dengan 1, semua bidang akan diinisialisasi ke unit. Karena itu, gaya penulisan ini lebih berbahaya daripada berguna.

Alat analisa PVS-Studio bahkan memiliki diagnosis terkait V1009 pada topik ini, deskripsi yang sekarang akan saya berikan.

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

Analyzer mendeteksi kemungkinan kesalahan karena fakta bahwa ketika mendeklarasikan array, nilainya ditentukan hanya untuk satu elemen. Dengan demikian, sisa elemen akan diinisialisasi secara implisit ke nol atau konstruktor default.

Pertimbangkan contoh kode yang mencurigakan:

 int arr[3] = {1}; 

Mungkin programmer diharapkan arr terdiri dari satu unit, tetapi ini tidak demikian. Array akan terdiri dari nilai 1, 0, 0.

Kode yang benar adalah:

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

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

Jika desain yang serupa digunakan secara aktif dalam proyek Anda, Anda dapat menonaktifkan diagnostik ini.

Juga tidak disarankan untuk mengabaikan visibilitas kode.

Misalnya, kode untuk pengkodean nilai warna ditulis sebagai berikut:

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

Berkat inisialisasi implisit, semua warna diatur dengan benar, tetapi lebih baik untuk 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, saya meminta Anda untuk me-refresh konten artikel " Mengapa penting untuk memeriksa apa fungsi malloc dikembalikan ". Artikel ini telah menghasilkan banyak diskusi dan kritik. Inilah beberapa diskusi: reddit.com/r/cpp , reddit.com/r/C_Programming , habr.com . Kadang-kadang mereka menulis kepada saya tentang artikel ini melalui pos sekarang.

Artikel ini dikritik oleh pembaca tentang hal-hal berikut:

1. Jika malloc mengembalikan NULL , maka lebih baik untuk segera keluar dari program daripada menulis seandainya dan mencoba untuk entah bagaimana menangani kekurangan memori, karena itu seringkali tidak mungkin untuk menjalankan program itu.

Saya sama sekali tidak menelepon sampai yang terakhir untuk berurusan dengan konsekuensi kehabisan memori, melemparkan kesalahan lebih tinggi dan lebih tinggi. Jika diizinkan untuk aplikasi dimatikan tanpa peringatan, maka jadilah itu. Untuk melakukan ini, hanya satu pemeriksaan diperlukan segera setelah malloc atau menggunakan xmalloc (lihat paragraf berikutnya).

Saya keberatan dan memperingatkan tentang tidak adanya cek, karena itu program terus bekerja "seolah-olah tidak ada yang terjadi." Ini sangat berbeda. Ini berbahaya karena mengarah pada perilaku yang tidak terdefinisi, korupsi data, dan sebagainya.

2. Tidak membicarakan solusi, yang terdiri dari penulisan fungsi-pembungkus untuk mengalokasikan memori dengan verifikasi selanjutnya atau menggunakan fungsi yang ada, seperti xmalloc .

Saya setuju, saya melewatkan momen ini. Saat menulis artikel, saya hanya tidak memikirkan bagaimana cara memperbaiki situasi. Lebih penting bagi saya untuk menyampaikan kepada pembaca tentang bahaya kurangnya verifikasi. Cara memperbaiki kesalahan sudah menjadi masalah selera dan detail implementasi.

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

Inti dari fungsi ini adalah bahwa program berakhir jika alokasi memori gagal. Implementasi fungsi ini mungkin terlihat seperti ini:

 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 di mana-mana alih-alih malloc, Anda dapat yakin bahwa program tidak akan memiliki perilaku yang tidak terdefinisi karena penggunaan pointer nol.

Sayangnya, xmalloc juga bukan obat mujarab untuk semua penyakit. Harus diingat 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 tidak sendiri mengerti bahwa ini adalah pendekatan yang salah. Saya sangat menyukai komentar ini dalam dukungan saya:

Dari pengalaman mendiskusikan topik semacam itu, orang merasa bahwa ada dua sekte di Internet. Penganut yang pertama sangat yakin bahwa di Linux, malloc tidak pernah mengembalikan NULL. Penganut yang kedua sangat yakin bahwa jika memori dalam program tidak dapat dialokasikan, tetapi tidak ada yang dapat dilakukan pada prinsipnya, Anda hanya perlu jatuh. Anda tidak dapat meyakinkan mereka dengan cara apa pun. Terutama ketika kedua sekte ini bersinggungan. Anda hanya bisa menerima begitu saja. Dan tidak masalah sumber daya profil tempat diskusi itu berlangsung.

Saya berpikir dan memutuskan untuk mengikuti saran dan saya tidak akan mencoba untuk membujuk :). Mari berharap tim pengembangan ini hanya menulis program yang tidak penting. Jika, misalnya, beberapa data memburuk dalam game atau game crash, itu bukan masalah besar.

Satu-satunya hal yang penting adalah pengembang perpustakaan, database, dll. Tidak melakukan ini.

Panggilan ke perpustakaan dan pengembang kode yang bertanggung jawab


Jika Anda sedang mengembangkan perpustakaan atau kode yang bertanggung jawab lainnya, selalu periksa nilai pointer yang dikembalikan oleh fungsi malloc / realloc , dan kembalikan kode kesalahan di luar jika memori tidak dapat dialokasikan.

Di perpustakaan, Anda tidak dapat memanggil fungsi keluar jika Anda tidak dapat mengalokasikan memori. Untuk alasan yang sama, Anda tidak dapat menggunakan xmalloc . Untuk banyak aplikasi, tidak dapat diterima hanya mengambil dan menghentikannya. Karena itu, misalnya, basis data mungkin rusak. Data yang telah dihitung berjam-jam mungkin hilang. Karena itu, program ini mungkin rentan terhadap penolakan kerentanan layanan, ketika alih-alih menangani dengan benar beban yang bertambah, aplikasi multithreaded berakhir begitu saja.

Tidak dapat diasumsikan bagaimana dan dalam proyek apa perpustakaan akan digunakan. Oleh karena itu, harus diasumsikan bahwa aplikasi dapat menyelesaikan tugas yang sangat penting. Dan hanya "membunuhnya" dengan memanggil keluar tidak baik. Kemungkinan besar, program semacam itu ditulis dengan mempertimbangkan kemungkinan kehabisan memori dan dapat melakukan sesuatu dalam kasus ini. Sebagai contoh, beberapa sistem CAD, karena fragmentasi memori yang kuat, tidak dapat mengalokasikan buffer memori yang cukup untuk operasi selanjutnya. Tapi ini bukan alasan baginya untuk berakhir dalam mode darurat dengan kehilangan data. Suatu program dapat memungkinkan untuk menyimpan proyek dan memulai kembali dengan sendirinya dalam mode normal.

Dalam kasus apa pun Anda tidak dapat mengandalkan fakta bahwa malloc selalu dapat mengalokasikan memori. Tidak diketahui pada platform apa dan bagaimana perpustakaan akan digunakan. Jika pada satu platform kekurangan memori adalah eksotis, maka di platform lain itu bisa menjadi situasi yang sangat umum.

Tidak dapat diharapkan bahwa jika malloc mengembalikan NULL , maka program akan macet. Apa pun bisa terjadi. Seperti yang saya jelaskan dalam artikel , sebuah program dapat menulis data pada alamat yang tidak nol sama sekali. Akibatnya, beberapa data mungkin rusak, yang mengarah pada konsekuensi yang tidak terduga. Bahkan memset itu berbahaya. Jika data diisi dengan urutan terbalik, maka pada awalnya beberapa data rusak, dan hanya kemudian program akan crash. Tetapi musim gugur bisa terjadi terlambat. Jika data yang rusak digunakan dalam utas paralel pada saat fungsi memset , maka konsekuensinya bisa fatal. Anda bisa mendapatkan transaksi yang rusak di database atau mengirim perintah untuk menghapus file yang "tidak perlu". Apa pun bisa terjadi dalam waktu. Saya menyarankan pembaca untuk membayangkan secara mandiri apa yang dapat menyebabkan penggunaan sampah dalam memori.

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

Tautan situs


  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 .



Jika Anda ingin berbagi artikel ini dengan audiens yang berbahasa Inggris, silakan gunakan tautan ke terjemahan: Andrey Karpov. Periksa Chromium Keenam, Kata Penutup .

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


All Articles