Apa artinya melakukan "cek kosong" di C atau C ++?

21

Saya telah belajar C ++ dan saya kesulitan memahami nol. Secara khusus, tutorial yang saya baca menyebutkan melakukan "cek kosong", tapi saya tidak yakin apa artinya atau mengapa itu perlu.

  • Apa itu null?
  • Apa artinya "memeriksa null"?
  • Apakah saya selalu perlu memeriksa nol?

Setiap contoh kode akan sangat dihargai.

kdi
sumber
Kapan harus: programmers.stackexchange.com/questions/186036/…
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件
Saya akan menyarankan untuk mendapatkan beberapa tutorial yang lebih baik, jika semua yang Anda baca berbicara tentang cek kosong tanpa pernah menjelaskannya dan memberikan contoh kode ...
underscore_d

Jawaban:

26

Dalam C dan C ++, pointer secara inheren tidak aman, yaitu, ketika Anda melakukan dereferensi pointer, itu adalah tanggung jawab Anda sendiri untuk memastikan itu menunjuk ke suatu tempat yang valid; ini adalah bagian dari "manajemen memori manual" (bukan skema manajemen memori otomatis yang diterapkan dalam bahasa seperti Java, PHP, atau .NET runtime, yang tidak akan memungkinkan Anda membuat referensi yang tidak valid tanpa usaha yang cukup).

Solusi umum yang menangkap banyak kesalahan adalah dengan mengatur semua pointer yang tidak menunjuk ke apa pun sebagai NULL(atau, dalam C ++ yang benar, 0), dan memeriksa untuk itu sebelum mengakses pointer. Secara khusus, itu adalah praktik umum untuk menginisialisasi semua pointer ke NULL (kecuali Anda sudah memiliki sesuatu untuk mengarahkan mereka ketika Anda mendeklarasikannya), dan mengaturnya ke NULL ketika Anda deleteatau free()mereka (kecuali mereka keluar dari ruang lingkup segera setelah itu). Contoh (dalam C, tetapi juga valid C ++):

void fill_foo(int* foo) {
    *foo = 23; // this will crash and burn if foo is NULL
}

Versi yang lebih baik:

void fill_foo(int* foo) {
    if (!foo) { // this is the NULL check
        printf("This is wrong\n");
        return;
    }
    *foo = 23;
}

Tanpa tanda centang nol, meneruskan pointer NULL ke fungsi ini akan menyebabkan segfault, dan tidak ada yang dapat Anda lakukan - OS hanya akan membunuh proses Anda dan mungkin core-dump atau pop up dialog laporan kerusakan. Dengan tanda centang nol di tempat, Anda dapat melakukan penanganan kesalahan yang tepat dan memulihkan dengan anggun - perbaiki sendiri masalahnya, batalkan operasi saat ini, tulis entri log, beri tahu pengguna, apa pun yang sesuai.

tammmer
sumber
3
@ Tuan tuan apa maksudmu, cek nol tidak bekerja di C ++? Anda hanya perlu menginisialisasi pointer ke nol saat Anda mendeklarasikannya.
TZHX
1
Maksud saya adalah, Anda harus ingat untuk mengatur pointer ke NULL atau itu tidak akan berfungsi. Dan jika Anda ingat, dengan kata lain jika Anda tahu bahwa penunjuknya adalah NULL, Anda tidak akan perlu memanggil fill_foo. fill_foo memeriksa apakah pointer memiliki nilai, bukan apakah pointer memiliki nilai yang valid . Dalam C ++, pointer tidak dijamin NULL memiliki nilai yang valid.
Tn. Lister
4
Assert () akan menjadi solusi yang lebih baik di sini. Tidak ada gunanya mencoba "aman". Jika NULL disahkan, itu jelas salah, jadi mengapa tidak crash saja untuk membuat programmer sepenuhnya sadar? (Dan dalam produksi, itu tidak masalah, karena Anda telah membuktikan bahwa tidak ada yang akan memanggil fill_foo () dengan NULL, kan? Sungguh, itu tidak sulit.)
Ambroz Bizjak
7
Jangan lupa untuk menyebutkan bahwa versi yang lebih baik dari fungsi ini harus menggunakan referensi daripada pointer, membuat pemeriksaan NULL menjadi usang.
Doc Brown
4
Ini bukan tentang manajemen memori manual, dan program yang dikelola akan meledak juga, (atau menaikkan pengecualian, seperti program asli dalam kebanyakan bahasa), jika Anda mencoba untuk referensi referensi nol.
Mason Wheeler
7

Jawaban lain cukup banyak mencakup pertanyaan Anda. Pemeriksaan nol dilakukan untuk memastikan bahwa pointer yang Anda terima benar-benar menunjuk ke instance yang valid dari suatu tipe (objek, primitif, dll.).

Saya akan menambahkan saran saya sendiri di sini. Hindari cek nol. :) Null memeriksa (dan bentuk-bentuk lain dari Pemrograman Defensif) mengacaukan kode, dan benar-benar membuatnya lebih rentan kesalahan daripada teknik penanganan kesalahan lainnya.

Teknik favorit saya ketika datang ke pointer objek adalah dengan menggunakan pola Obyek Null . Itu berarti mengembalikan (pointer - atau bahkan lebih baik, referensi ke) array kosong atau daftar bukan nol, atau mengembalikan string kosong ("") bukannya nol, atau bahkan string "0" (atau sesuatu yang setara dengan "tidak ada" "dalam konteks) tempat Anda mengharapkannya diuraikan menjadi integer.

Sebagai bonus, berikut ini adalah sesuatu yang mungkin tidak Anda ketahui tentang null pointer, yang (pertama kali secara resmi) diimplementasikan oleh CAR Hoare untuk bahasa Algol W pada tahun 1965.

Saya menyebutnya kesalahan miliaran dolar saya. Itu adalah penemuan referensi nol pada tahun 1965. Pada waktu itu, saya sedang merancang sistem tipe komprehensif pertama untuk referensi dalam bahasa berorientasi objek (ALGOL W). Tujuan saya adalah untuk memastikan bahwa semua penggunaan referensi harus benar-benar aman, dengan pengecekan dilakukan secara otomatis oleh kompiler. Tapi saya tidak bisa menahan godaan untuk memasukkan referensi nol, hanya karena itu sangat mudah diimplementasikan. Ini telah menyebabkan kesalahan yang tak terhitung banyaknya, kerentanan, dan crash sistem, yang mungkin telah menyebabkan satu miliar dolar rasa sakit dan kerusakan dalam empat puluh tahun terakhir.

Yam Marcovic
sumber
6
Objek Null bahkan lebih buruk daripada hanya memiliki pointer nol. Jika suatu algoritma X membutuhkan data Y yang tidak Anda miliki, maka itu adalah bug dalam program Anda , yang Anda sembunyikan dengan berpura-pura bahwa Anda melakukannya.
DeadMG
Itu tergantung pada konteksnya, dan bagaimanapun juga pengujian untuk "keberadaan data" mengalahkan pengujian nol di buku saya. Dari pengalaman saya, jika suatu algoritma bekerja pada, katakanlah, daftar, dan daftar itu kosong, maka algoritma itu tidak ada hubungannya, dan itu dicapai dengan hanya menggunakan pernyataan kontrol standar seperti untuk / foreach.
Yam Marcovic
Jika algoritme tidak ada hubungannya, lalu mengapa Anda memanggilnya? Dan alasan Anda mungkin ingin menyebutnya di tempat pertama adalah karena melakukan sesuatu yang penting .
DeadMG
@DeadMG Karena program adalah tentang input, dan di dunia nyata, tidak seperti tugas pekerjaan rumah, input bisa tidak relevan (mis. Kosong). Kode masih dipanggil dengan cara apa pun. Anda memiliki dua opsi: Anda memeriksa relevansi (atau kekosongan), atau Anda mendesain algoritme Anda sehingga mereka membaca dan bekerja dengan baik tanpa secara eksplisit memeriksa relevansi menggunakan pernyataan kondisional.
Yam Marcovic
Saya datang ke sini untuk membuat komentar yang hampir sama, jadi berikan Anda suara saya sebagai gantinya. Namun, saya juga akan menambahkan bahwa ini mewakili masalah yang lebih besar dari objek zombie - kapan saja Anda memiliki objek dengan inisialisasi multi-tahap (atau penghancuran) yang tidak sepenuhnya hidup tetapi tidak cukup mati. Ketika Anda melihat kode "aman" dalam bahasa tanpa finalisasi deterministik yang telah menambahkan pemeriksaan di setiap fungsi untuk melihat apakah objek telah dibuang, itu adalah masalah umum saat memundurkan kepalanya. Anda seharusnya tidak pernah-null, Anda harus bekerja dengan negara yang memiliki objek yang mereka butuhkan untuk seumur hidup mereka.
ex0du5
4

Nilai penunjuk nol mewakili "tempat" yang didefinisikan dengan baik; itu adalah nilai pointer yang tidak valid yang dijamin untuk membandingkan tidak sama dengan nilai pointer lainnya. Mencoba untuk melakukan dereferensi hasil pointer nol dalam perilaku tidak terdefinisi, dan biasanya akan menyebabkan kesalahan runtime, jadi Anda ingin memastikan pointer tidak NULL sebelum mencoba melakukan dereferensi. Sejumlah fungsi pustaka C dan C ++ akan mengembalikan pointer nol untuk menunjukkan kondisi kesalahan. Misalnya, fungsi pustaka mallocakan mengembalikan nilai pointer nol jika tidak dapat mengalokasikan jumlah byte yang telah diminta, dan mencoba mengakses memori melalui pointer itu (biasanya) akan menyebabkan kesalahan runtime:

int *p = malloc(sizeof *p * N);
p[0] = ...; // this will (usually) blow up if malloc returned NULL

Jadi kita perlu memastikan mallocpanggilan berhasil dengan memeriksa nilai pterhadap NULL:

int *p = malloc(sizeof *p * N);
if (p != NULL) // or just if (p)
  p[0] = ...;

Sekarang, tunggu sebentar, ini akan sedikit bergelombang.

Ada nilai pointer nol dan konstanta pointer nol , dan keduanya tidak harus sama. Nilai penunjuk nol adalah nilai apa pun yang digunakan arsitektur dasar untuk mewakili "tempat". Nilai ini mungkin 0x00000000, atau 0xFFFFFFFF, atau 0xDEADBEEF, atau sesuatu yang sama sekali berbeda. Jangan berasumsi bahwa pointer nol nilai selalu 0.

Konstanta penunjuk nol , OTOH, selalu merupakan ekspresi integral bernilai 0. Sejauh menyangkut kode sumber Anda , 0 (atau ekspresi integral apa pun yang bernilai 0) mewakili penunjuk nol. Baik C dan C ++ mendefinisikan makro NULL sebagai konstanta penunjuk nol. Ketika kode Anda dikompilasi, null pointer konstan akan diganti dengan nol pointer yang sesuai nilai dalam kode mesin yang dihasilkan.

Perlu diketahui juga bahwa NULL hanyalah salah satu dari banyak nilai pointer yang mungkin tidak valid ; jika Anda mendeklarasikan variabel penunjuk otomatis tanpa menginisialisasi secara eksplisit, seperti

int *p;

nilai awalnya disimpan dalam variabel tidak tentu , dan mungkin tidak sesuai dengan alamat memori yang valid atau dapat diakses. Sayangnya, tidak ada cara (portabel) untuk mengetahui apakah nilai pointer non-NULL valid atau tidak sebelum mencoba menggunakannya. Jadi jika Anda berurusan dengan pointer, biasanya ide yang baik untuk secara eksplisit menginisialisasi mereka ke NULL ketika Anda mendeklarasikannya, dan untuk mengaturnya ke NULL ketika mereka tidak secara aktif menunjuk ke apa pun.

Perhatikan bahwa ini lebih merupakan masalah dalam C daripada C ++; C ++ idiomatis seharusnya tidak menggunakan pointer sebanyak itu.

John Bode
sumber
3

Ada beberapa metode, semua pada dasarnya melakukan hal yang sama.

int * foo = NULL; // terkadang diatur ke 0x00 atau 0 atau 0L sebagai ganti NULL

cek nol (periksa apakah pointernya nol), versi A

if (foo == NULL)

cek nol, versi B

if (! foo) // karena NULL didefinisikan sebagai 0,! foo akan mengembalikan nilai dari pointer nol

cek nol, versi C

if (foo == 0)

Dari ketiganya, saya lebih suka menggunakan cek pertama karena secara eksplisit memberi tahu pengembang masa depan apa yang Anda coba periksa DAN itu membuat jelas bahwa Anda diharapkan foo menjadi pointer.


sumber
2

Kamu tidak. Satu-satunya alasan untuk menggunakan pointer di C ++ adalah karena Anda secara eksplisit menginginkan keberadaan null pointer; selain itu, Anda dapat mengambil referensi, yang secara semantik lebih mudah digunakan dan menjamin non-null.

DeadMG
sumber
1
@ James: 'baru' dalam mode kernel?
Nemanja Trifunovic
1
@ James: Implementasi C ++ yang mewakili kemampuan yang dinikmati oleh sebagian besar coders C ++. Itu mencakup semua fitur bahasa C ++ 03 (kecuali export) dan semua fitur pustaka C ++ 03 dan TR1 dan sebagian besar C ++ 11.
DeadMG
5
Saya melakukan keinginan orang tidak akan mengatakan bahwa "referensi menjamin non-null." Mereka tidak melakukannya. Sangat mudah untuk menghasilkan referensi nol sebagai pointer nol, dan mereka menyebarkan dengan cara yang sama.
mjfgates
2
@Stargazer: Pertanyaannya 100% berlebihan ketika Anda hanya menggunakan alat seperti yang disarankan oleh perancang bahasa dan praktik yang baik.
DeadMG
2
@DeadMG, tidak masalah apakah itu berlebihan. Anda tidak menjawab pertanyaan itu . Saya akan mengatakannya lagi: -1.
riwalk
-1

Jika Anda tidak memeriksa nilai NULL, khususnya, jika itu adalah pointer ke struct, Anda mungkin bertemu dengan kerentanan keamanan - Nere pointer dereference. NULL pointer dereference dapat menyebabkan beberapa kerentanan keamanan serius lainnya seperti buffer overflow, kondisi ras ... yang dapat memungkinkan penyerang mengendalikan komputer Anda.

Banyak vendor perangkat lunak seperti Microsoft, Oracle, Adobe, Apple ... merilis patch perangkat lunak untuk memperbaiki kerentanan keamanan ini. Saya pikir Anda harus memeriksa nilai NULL dari setiap pointer :)

oDisPo
sumber