Memeriksa penunjuk NULL dalam C / C ++ [ditutup]

153

Dalam ulasan kode baru-baru ini, seorang kontributor mencoba untuk menegakkan bahwa semua NULLpemeriksaan pada pointer dilakukan dengan cara berikut:

int * some_ptr;
// ...
if (some_ptr == NULL)
{
    // Handle null-pointer error
}
else
{
    // Proceed
}

dari pada

int * some_ptr;
// ...
if (some_ptr)
{
    // Proceed
}
else
{
    // Handle null-pointer error
}

Saya setuju bahwa caranya sedikit lebih jelas dalam arti bahwa itu secara eksplisit mengatakan "Pastikan pointer ini bukan NULL", tetapi saya akan membantahnya dengan mengatakan bahwa siapa pun yang bekerja pada kode ini akan memahami bahwa menggunakan variabel pointer dalam sebuah if Pernyataan secara implisit memeriksa NULL. Saya juga merasa metode kedua memiliki peluang lebih kecil untuk memperkenalkan bug sejenisnya:

if (some_ptr = NULL)

yang merupakan rasa sakit mutlak untuk menemukan dan men-debug.

Cara mana yang Anda sukai dan mengapa?

Bryan Marble
sumber
32
Memiliki gaya yang konsisten terkadang lebih penting daripada gaya yang Anda pilih.
Mark Ransom
15
@ Mark: Konsistensi selalu menjadi faktor terpenting gaya Anda.
sbi
13
"Saya juga merasa metode kedua memiliki peluang lebih kecil untuk memperkenalkan bug dari sejenisnya: if (some_ptr = NULL)" Itulah mengapa beberapa orang menggunakan if (NULL == some_ptr), tidak dapat menetapkan ke NULL sehingga Anda akan mendapatkan kesalahan kompilasi.
user122302
14
kebanyakan kompiler hari ini memperingatkan tentang penugasan dalam kondisi - sayangnya terlalu banyak dev mengabaikan peringatan kompiler.
Mike Ellery
10
Saya tidak setuju dengan pernyataan di atas bahwa konsistensi adalah yang penting. Ini tidak ada hubungannya dengan konsistensi; bukan jenis yang baik pula. Ini adalah area di mana kita harus membiarkan programmer mengekspresikan gaya mereka sendiri. Jika ada orang yang tidak segera memahami formulir mana pun maka mereka harus segera berhenti membaca kode dan mempertimbangkan perubahan karier. Saya akan mengatakan lebih dari itu: Jika ada konsistensi kosmetik Anda tidak dapat secara otomatis menegakkan dengan skrip kemudian menghapusnya. Biaya menguras moral manusia adalah WAY lebih penting daripada keputusan bodoh yang dibuat orang dalam rapat.
wilhelmtell

Jawaban:

203

Dalam pengalaman saya, tes formulir if (ptr)atau if (!ptr)lebih disukai. Mereka tidak bergantung pada definisi simbolNULL . Mereka tidak mengekspos kesempatan untuk penugasan yang tidak disengaja itu. Dan mereka jelas dan ringkas.

Sunting: Seperti yang ditunjukkan SoapBox dalam komentar, mereka kompatibel dengan kelas C ++ seperti auto_ptritu adalah objek yang bertindak sebagai pointer dan yang menyediakan konversi booluntuk mengaktifkan idiom ini dengan tepat. Untuk objek-objek ini, perbandingan eksplisit NULLharus memohon konversi ke pointer yang mungkin memiliki efek samping semantik lain atau lebih mahal daripada keberadaan sederhana memeriksa bahwabool menyiratkan konversi.

Saya memiliki preferensi untuk kode yang mengatakan apa artinya tanpa teks yang tidak dibutuhkan. if (ptr != NULL)memiliki arti yang sama if (ptr)tetapi dengan biaya spesifisitas yang berlebihan. Hal logis berikutnya adalah menulis if ((ptr != NULL) == TRUE)dan dengan cara itu ada kegilaan. Bahasa C jelas bahwa boolean diuji oleh if, whileatau sejenisnya memiliki arti spesifik dari nilai bukan nol adalah benar dan nol adalah salah. Redundansi tidak membuatnya lebih jelas.

RBerteig
sumber
23
Selain itu, mereka kompatibel dengan kelas pembungkus pointer (shared_ptr, auto_ptr, scoped_tr, dll) yang biasanya menimpa bool operator (atau safe_bool).
SoapBox
2
Saya memilih ini sebagai "jawaban" hanya karena referensi untuk menambahkan auto_ptr ke diskusi. Setelah menulis pertanyaan, jelas bahwa ini sebenarnya lebih subjektif daripada yang lain dan mungkin tidak cocok untuk "jawaban" stackoverflow karena semua ini bisa "benar" tergantung pada keadaan.
Bryan Marble
2
@Bryan, saya menghargai itu, dan jika ada jawaban "lebih baik", jangan ragu untuk memindahkan tanda centang yang diperlukan. Adapun subjektivitasnya, ya itu subyektif. Tapi setidaknya diskusi subyektif di mana ada masalah obyektif yang tidak selalu jelas ketika pertanyaan diajukan. Garisnya buram. Dan subjektif ... ;-)
RBerteig
1
Satu hal penting yang perlu diperhatikan: sampai saat ini, saya lebih suka menulis "jika (ptr)" daripada "jika (ptr! = NULL)" atau "jika (ptr! = Nullptr)" karena lebih ringkas sehingga membuat kode lebih mudah dibaca. Tapi saya baru tahu bahwa perbandingan menimbulkan (resp.) Peringatan / kesalahan untuk perbandingan dengan NULL / nullptr pada kompiler baru-baru ini. Jadi jika Anda ingin memeriksa sebuah pointer, lebih baik lakukan "if (ptr! = NULL)" daripada "if (ptr)". Jika Anda secara keliru mendeklarasikan ptr sebagai int, pemeriksaan pertama akan memunculkan peringatan / kesalahan, sedangkan yang terakhir akan mengkompilasi tanpa peringatan apa pun.
Arnaud
1
@ David 天宇 Wong Tidak, bukan itu yang dimaksud. Contoh pada halaman tersebut adalah dua urutan baris di int y = *x; if (!x) {...}mana pengoptimal diizinkan untuk alasan bahwa karena *xakan ditentukan jika xNULL, itu tidak boleh NULL dan karenanya !xharus FALSE. Ungkapan !ptritu bukan perilaku yang tidak terdefinisi. Dalam contoh, jika tes dilakukan sebelum penggunaan *x, maka pengoptimal akan salah untuk menghilangkan tes.
RBerteig
52

if (foo)cukup jelas. Gunakan.

wilx
sumber
25

Saya akan mulai dengan ini: konsistensi adalah raja, keputusan itu kurang penting daripada konsistensi dalam basis kode Anda.

Dalam C ++

NULL didefinisikan sebagai 0 atau 0L dalam C ++.

Jika Anda sudah membaca Bahasa Pemrograman C ++ Bjarne Stroustrup menyarankan menggunakan 0secara eksplisit untuk menghindari NULLmakro ketika melakukan tugas , saya tidak yakin apakah dia melakukan hal yang sama dengan perbandingan, sudah lama sejak saya membaca buku, saya pikir dia baru saja melakukannya if(some_ptr)tanpa perbandingan eksplisit tapi saya tidak jelas tentang itu.

Alasan untuk ini adalah bahwa NULLmakro itu menipu (karena hampir semua makro) itu sebenarnya 0literal, bukan tipe unik seperti namanya. Menghindari makro adalah salah satu pedoman umum dalam C ++. Di sisi lain, 0tampak seperti bilangan bulat dan tidak bila dibandingkan dengan atau ditugaskan ke pointer. Secara pribadi saya bisa memilih jalan yang lain, tetapi biasanya saya melewatkan perbandingan eksplisit (meskipun beberapa orang tidak suka ini yang mungkin mengapa Anda memiliki kontributor yang menyarankan perubahan).

Terlepas dari perasaan pribadi ini sebagian besar merupakan pilihan yang paling tidak jahat karena tidak ada satu metode yang benar.

Ini jelas dan idiom yang umum dan saya lebih suka, tidak ada kesempatan untuk secara tidak sengaja memberikan nilai selama perbandingan dan itu terbaca dengan jelas:

if(some_ptr){;}

Ini jelas jika Anda tahu itu some_ptradalah tipe pointer, tetapi mungkin juga terlihat seperti perbandingan integer:

if(some_ptr != 0){;}

Ini jelas, dalam kasus umum masuk akal ... Tapi ini abstraksi yang bocor, NULLsebenarnya 0harfiah dan akhirnya bisa disalahgunakan dengan mudah:

if(some_ptr != NULL){;}

C ++ 0x memiliki nullptr yang sekarang menjadi metode yang disukai karena eksplisit dan akurat, hanya berhati-hati tentang penugasan tak disengaja:

if(some_ptr != nullptr){;}

Sampai Anda dapat bermigrasi ke C ++ 0x saya berpendapat itu buang-buang waktu mengkhawatirkan metode mana yang Anda gunakan, mereka semua tidak cukup itulah sebabnya nullptr ditemukan (bersama dengan masalah pemrograman generik yang muncul dengan penerusan sempurna .) Yang paling penting adalah menjaga konsistensi.

Dalam C

C adalah binatang yang berbeda.

Dalam C NULL dapat didefinisikan sebagai 0 atau sebagai ((batal *) 0), C99 memungkinkan untuk implementasi konstanta penunjuk nol yang ditentukan. Jadi sebenarnya turun ke definisi implementasi NULL dan Anda harus memeriksanya di perpustakaan standar Anda.

Macro sangat umum dan secara umum mereka banyak digunakan untuk menebus kekurangan dalam dukungan pemrograman generik dalam bahasa dan hal-hal lain juga. Bahasanya jauh lebih sederhana dan lebih mengandalkan pre-processor.

Dari perspektif ini saya mungkin akan merekomendasikan menggunakan NULLdefinisi makro dalam C.

M2tM
sumber
tl; dr dan meskipun Anda benar sedang tentang 0di C ++, itu memiliki makna dalam C: (void *)0. 0lebih disukai NULLdi C ++, karena kesalahan tipe bisa menjadi PITA.
Matt Joiner
@ Matt: Tapi NULLtetap nol.
GManNickG
@ GM: Tidak di sistem saya: #define NULL ((void *)0)darilinux/stddef.h
Matt Joiner
@ Mat: Dalam C ++. Anda mengatakan 0 lebih disukai, tetapi NULLharus nol.
GManNickG
Ini adalah poin yang bagus, saya lalai menyebutkannya dan saya meningkatkan jawaban saya dengan menyarankannya. ANSI C dapat memiliki NULL didefinisikan sebagai ((void *) 0), C ++ mendefinisikan NULL sebagai 0. Saya belum menjaring standar untuk ini secara langsung tetapi pemahaman saya adalah bahwa dalam C ++ NULL dapat 0 atau 0L.
M2tM
19

Saya menggunakan if (ptr), tapi ini sama sekali tidak layak diperdebatkan.

Saya suka cara saya karena ringkas, meskipun yang lain mengatakan == NULLmembuatnya lebih mudah untuk dibaca dan lebih eksplisit. Saya melihat dari mana mereka berasal, saya hanya tidak setuju dengan hal-hal tambahan yang membuatnya lebih mudah. (Saya benci makro, jadi saya bias.) Terserah Anda.

Saya tidak setuju dengan argumen Anda. Jika Anda tidak mendapatkan peringatan untuk penugasan dalam kondisi bersyarat, Anda perlu menaikkan level peringatan Anda. Sederhana seperti itu. (Dan untuk cinta semua yang baik, jangan mengalihkan mereka.)

Catatan di C ++ 0x, bisa kita lakukan if (ptr == nullptr), yang bagi saya memang lebih bagus. (Sekali lagi, saya benci makro. Tapi nullptritu bagus.) Namun, saya tetap melakukannya if (ptr), hanya karena itulah yang biasa saya lakukan.

GManNickG
sumber
harap ekstra 5 tahun pengalaman berubah pikiran :) jika C ++ / C memiliki operator seperti if_exist (), maka itu masuk akal.
magulla
10

Terus terang, saya tidak mengerti mengapa itu penting. Salah satunya cukup jelas dan siapa pun yang cukup berpengalaman dengan C atau C ++ harus memahami keduanya. Namun satu komentar:

Jika Anda berencana untuk mengenali kesalahan dan tidak melanjutkan menjalankan fungsi (yaitu, Anda akan melempar pengecualian atau mengembalikan kode kesalahan segera), Anda harus menjadikannya klausa penjaga:

int f(void* p)
{
    if (!p) { return -1; }

    // p is not null
    return 0;
}

Dengan cara ini, Anda menghindari "kode panah."

James McNellis
sumber
seseorang menunjuk ke sini kukuruku.co/hub/programming/i-do-not-know-c yang if(!p)merupakan perilaku yang tidak terdefinisi. Itu bisa berhasil atau tidak.
David 天宇 Wong
@ David 天宇 Wong jika Anda berbicara tentang contoh # 2 di tautan itu, if(!p)itu bukan perilaku yang tidak ditentukan, itu adalah baris sebelumnya. Dan if(p==NULL)akan memiliki masalah yang sama persis.
Mark Ransom
8

Secara pribadi saya selalu menggunakan if (ptr == NULL)karena itu membuat maksud saya eksplisit, tetapi pada titik ini hanya kebiasaan.

Penggunaan =di tempat ==akan ditangkap oleh kompiler yang kompeten dengan pengaturan peringatan yang benar.

Poin penting adalah memilih gaya yang konsisten untuk grup Anda dan tetap menggunakannya. Tidak peduli ke arah mana Anda pergi, pada akhirnya Anda akan terbiasa, dan kehilangan gesekan saat bekerja dengan kode orang lain akan diterima.

Mark tebusan
sumber
7

Hanya satu poin lagi yang mendukung foo == NULLpraktik ini: Jika fooadalah, katakanlah, satu int *atau a bool *, maka if (foo)cek tersebut dapat secara tidak sengaja ditafsirkan oleh pembaca sebagai pengujian nilai pointee, yaitu sebagai if (*foo). The NULLperbandingan di sini adalah pengingat bahwa kita sedang berbicara tentang pointer.

Tapi saya kira konvensi penamaan yang baik membuat argumen ini bisa diperdebatkan.

Daniel Hershcovich
sumber
4

Sebenarnya, saya menggunakan kedua varian.

Ada situasi, di mana Anda pertama kali memeriksa validitas pointer, dan jika itu NULL, Anda cukup kembali / keluar dari suatu fungsi. (Saya tahu ini dapat mengarah pada diskusi "jika suatu fungsi hanya memiliki satu titik keluar")

Sebagian besar waktu, Anda memeriksa pointer, lalu melakukan apa yang Anda inginkan dan kemudian menyelesaikan kasus kesalahan. Hasilnya bisa berupa kode indentasi x-times jelek dengan multiple if's.

dwo
sumber
1
Saya pribadi benci kode panah yang akan menghasilkan menggunakan satu titik keluar. Mengapa tidak keluar jika saya sudah tahu jawabannya?
ruslik
1
@ruslik: di C (atau C-with-class style C ++), memiliki beberapa pengembalian membuat pembersihan lebih sulit. Dalam "nyata" C ++ itu jelas bukan masalah karena pembersihan Anda ditangani oleh RAII, tetapi beberapa programmer (untuk alasan yang kurang lebih valid) terjebak di masa lalu, dan entah menolak, atau tidak diizinkan, mengandalkan RAII .
jalf
@jalf dalam kasus seperti itu gotomungkin sangat berguna. Juga dalam banyak kasus malloc()yang perlu dibersihkan bisa diganti dengan yang lebih cepat alloca(). Saya tahu mereka tidak direkomendasikan, tetapi mereka ada karena suatu alasan (oleh saya, label dengan nama baik gotojauh lebih bersih daripada if/elsepohon yang dikaburkan ).
ruslik
1
allocatidak standar dan sama sekali tidak aman. Jika gagal, program Anda akan meledak. Tidak ada peluang untuk pemulihan, dan karena tumpukan relatif kecil, kemungkinan besar kegagalan. Jika gagal, Anda mungkin menghadapi tumpukan dan memperkenalkan kerentanan yang membahayakan hak istimewa. Jangan pernah gunakan allocaatau vlas kecuali Anda memiliki batasan kecil pada ukuran yang akan dialokasikan (dan kemudian Anda mungkin juga menggunakan array normal).
R .. GitHub BERHENTI MEMBANTU ICE
4

Bahasa Pemrograman C (K&R) akan meminta Anda memeriksa null == ptr untuk menghindari tugas yang tidak disengaja.

Derek
sumber
23
K&R juga akan bertanya "apa itu penunjuk pintar?" Banyak hal berubah di dunia C ++.
Alex Emelianov
2
atau lebih tepatnya, semuanya sudah berubah di dunia C ++ ";)
jalf
3
Di mana K&R menyatakan itu (ref)? Mereka tidak pernah menggunakan gaya ini sendiri. Dalam K & R2 bab 2 mereka bahkan merekomendasikan di if (!valid)atas if (valid == 0).
schot
6
Tenang, teman-teman. Dia mengatakan k & r, bukan K&R. Mereka jelas kurang terkenal karena inisial mereka bahkan tidak dikapitalisasi.
jmucchiello
1
memanfaatkan hanya memperlambat Anda
Derek
3

Jika gaya dan format akan menjadi bagian dari ulasan Anda, harus ada panduan gaya yang disepakati untuk diukur. Jika ada, lakukan apa yang dikatakan panduan gaya. Jika tidak ada, detail seperti ini harus dibiarkan seperti yang tertulis. Ini buang-buang waktu dan energi, dan mengalihkan perhatian dari apa yang seharusnya diungkapkan oleh ulasan kode. Serius, tanpa panduan gaya saya akan mendorong untuk TIDAK mengubah kode seperti ini sebagai prinsip, bahkan ketika itu tidak menggunakan konvensi yang saya sukai.

Dan itu tidak penting, tetapi preferensi pribadi saya if (ptr). Maknanya lebih langsung jelas bagi saya daripada bahkanif (ptr == NULL) .

Mungkin dia mencoba mengatakan bahwa lebih baik menangani kondisi kesalahan sebelum jalan bahagia? Dalam hal ini saya masih tidak setuju dengan pengulas. Saya tidak tahu bahwa ada konvensi yang diterima untuk ini, tetapi menurut saya kondisi yang paling "normal" harus didahulukan dalam pernyataan if. Dengan begitu saya semakin sedikit menggali untuk mengetahui apa fungsi semua ini dan bagaimana cara kerjanya.

Pengecualian untuk ini adalah jika kesalahan menyebabkan saya menyelamatkan dari fungsi, atau saya dapat memulihkannya sebelum pindah. Dalam kasus tersebut, saya menangani kesalahan terlebih dahulu:

if (error_condition)
  bail_or_fix();
  return if not fixed;

// If I'm still here, I'm on the happy path

Dengan menghadapi kondisi yang tidak biasa di depan, saya bisa mengurusnya dan kemudian melupakannya. Tetapi jika saya tidak bisa kembali ke jalan bahagia dengan memegangnya di depan, maka itu harus ditangani setelah kasus utama karena membuat kode lebih mudah dimengerti. Menurutku.

Tetapi jika itu tidak ada dalam panduan gaya maka itu hanya pendapat saya, dan pendapat Anda sama validnya. Baik terstandarisasi atau tidak. Jangan biarkan resensi pseudo-standar hanya karena dia punya pendapat.

Darryl
sumber
1

Ini adalah salah satu dasar dari kedua bahasa yang pointer mengevaluasi untuk jenis dan nilai yang dapat digunakan sebagai ekspresi kontrol, booldalam C ++ dan intdalam C. Cukup gunakan.

Jens Gustedt
sumber
1
  • Pointer bukan boolean
  • Kompiler C / C ++ modern memancarkan peringatan saat Anda menulis if (foo = bar)secara tidak sengaja.

Karena itu saya lebih suka

if (foo == NULL)
{
    // null case
}
else
{
    // non null case
}

atau

if (foo != NULL)
{
    // non null case
}
else
{
    // null case
}

Namun, jika saya menulis seperangkat pedoman gaya saya tidak akan memasukkan hal-hal seperti ini di dalamnya, saya akan meletakkan hal-hal seperti:

Pastikan Anda melakukan pemeriksaan nol pada pointer.

JeremyP
sumber
2
Memang benar bahwa pointer bukan boolean, tetapi dalam C, jika-pernyataan tidak mengambil boolean: mereka mengambil ekspresi integer.
Ken
@ Ken: itu karena C rusak dalam hal itu. Secara konseptual, ini adalah ekspresi boolean dan (menurut saya) harus diperlakukan seperti itu.
JeremyP
1
Beberapa bahasa memiliki pernyataan if yang hanya menguji null / not-null. Beberapa bahasa memiliki pernyataan if yang hanya menguji integer untuk tanda (tes 3 arah). Saya tidak melihat alasan untuk menganggap C "rusak" karena mereka memilih konsep berbeda yang Anda sukai. Ada banyak hal yang saya benci tentang C tetapi itu berarti model program C tidak sama dengan model mental saya, bukan bahwa salah satu dari kita (saya atau C) rusak.
Ken
@ Ken: Boolean bukan angka atau penunjuk secara konseptual , Jangan pikirkan bahasa mana.
JeremyP
1
Saya tidak mengatakan bahwa boolean adalah angka atau penunjuk. Saya mengatakan tidak ada alasan untuk bersikeras bahwa pernyataan jika harus atau hanya bisa mengambil ekspresi boolean, dan menawarkan contoh tandingan, selain yang ada di tangan. Banyak bahasa mengambil sesuatu selain boolean, dan tidak hanya dengan cara "nol / bukan nol" dari C. Dalam komputasi, memiliki pernyataan if yang menerima (hanya) boolean adalah perkembangan yang relatif baru.
Ken
1

Saya penggemar berat fakta bahwa C / C ++ tidak memeriksa jenis dalam kondisi boolean if, fordan whilepernyataan. Saya selalu menggunakan yang berikut ini:

if (ptr)

if (!ptr)

bahkan pada bilangan bulat atau tipe lain yang diubah menjadi bool:

while(i--)
{
    // Something to do i times
}

while(cin >> a >> b)
{
    // Do something while you've input
}

Pengodean dalam gaya ini lebih mudah dibaca dan lebih jelas bagi saya. Hanya pendapat pribadi saya.

Baru-baru ini, ketika bekerja pada mikrokontroler OKI 431, saya perhatikan bahwa berikut ini:

unsigned char chx;

if (chx) // ...

lebih efisien daripada

if (chx == 1) // ...

karena dalam kasus selanjutnya kompiler harus membandingkan nilai chx ke 1. Di mana chx hanyalah flag true / false.

Donotalo
sumber
1

Sebagian besar kompiler yang saya gunakan setidaknya akan memperingatkan pada iftugas tanpa sintaks lebih lanjut, jadi saya tidak membeli argumen itu. Yang mengatakan, saya telah menggunakan keduanya secara profesional dan tidak memiliki preferensi untuk keduanya. Ini == NULLjelas lebih jelas menurut saya.

Michael Dorgan
sumber