Mengapa menggunakan bzero over memset?

156

Dalam kelas Pemrograman Sistem yang saya ambil semester sebelumnya ini, kami harus mengimplementasikan klien dasar / server di C. Ketika menginisialisasi struct, seperti sock_addr_in, atau buffer char (yang kami gunakan untuk mengirim data bolak-balik antara klien dan server) profesor menginstruksikan kami untuk hanya menggunakan bzerodan tidak memsetmenginisialisasi mereka. Dia tidak pernah menjelaskan mengapa, dan saya ingin tahu apakah ada alasan yang sah untuk ini?

Saya melihat di sini: http://fdiv.net/2009/01/14/memset-vs-bzero-ultimate-showdown yang bzerolebih efisien karena fakta bahwa hanya akan menjadi memori zeroing, jadi tidak harus melakukan pengecekan tambahan yang memsetmungkin dilakukan. Itu masih belum tentu tampak seperti alasan untuk sama sekali tidak menggunakan memsetuntuk mem-zero-up memori.

bzerodianggap usang, dan lebih jauh lagi bukan fungsi standar C. Menurut manual, memsetlebih disukai daripada bzerokarena alasan ini. Jadi, mengapa Anda ingin tetap menggunakan bzerolebih memset? Hanya untuk keuntungan efisiensi, atau itu sesuatu yang lebih? Demikian juga, apa manfaat dari memsetkelebihan bzeroyang menjadikannya pilihan yang dipilih secara de facto untuk program yang lebih baru?

PseudoPsyche
sumber
28
"Kenapa menggunakan bzero over memset?" - Jangan. Memset adalah standar, bzero tidak.
30
bzero adalah BSDism (). memset () adalah ansi-c. saat ini, bzero () mungkin akan diimplementasikan sebagai makro. Mintalah profesor Anda mencukur dirinya sendiri dan membaca beberapa buku. efisiensi adalah argumen palsu. Syscall atau context-switch dapat dengan mudah menghabiskan puluhan ribu kutu jam, satu kali melewati buffer berjalan pada kecepatan bus. Jika Anda ingin mengoptimalkan program-jaringan: meminimalkan jumlah syscalls (dengan membaca / menulis bongkahan besar)
wildplasser
7
Gagasan yang memsetmungkin sedikit kurang efisien karena "sedikit lebih banyak pengecekan" jelas merupakan kasus optimasi prematur: apa pun keuntungan yang mungkin Anda lihat dari menghilangkan satu atau dua instruksi CPU tidak sepadan ketika Anda dapat membahayakan portabilitas Anda. kode. bzerosudah usang, dan itu cukup alasan untuk tidak menggunakannya.
dasblinkenlight
4
Seringkali, Anda dapat menambahkan penginisialisasi `= {0}` sebagai gantinya, dan tidak memanggil fungsi sama sekali. Ini menjadi lebih mudah ketika sekitar pergantian abad C berhenti membutuhkan deklarasi variabel muka di muka. Beberapa perangkat kertas yang benar - benar tua masih terjebak jauh di abad sebelumnya.
MSalters
1
@SSAnne tidak, tetapi kemungkinan besar berasal dari buku yang direkomendasikan untuk kursus yang ia pengaruhi, sebagaimana disebutkan dalam salah satu jawaban di bawah ini: stackoverflow.com/a/17097072/1428743
PseudoPsyche

Jawaban:

152

Saya tidak melihat alasan untuk lebih memilih bzerolebih memset.

memsetadalah fungsi standar C sementara bzerotidak pernah menjadi fungsi standar C. Alasannya mungkin karena Anda dapat mencapai fungsionalitas yang sama persis menggunakan memsetfungsi.

Sekarang mengenai efisiensi, kompiler seperti gccmenggunakan implementasi builtin memsetyang beralih ke implementasi tertentu ketika konstanta 0terdeteksi. Sama untuk glibcsaat bawaan dinonaktifkan.

ouah
sumber
Terima kasih. Ini masuk akal. Saya cukup yakin bahwa memsetharus selalu digunakan dalam kasus ini, tetapi bingung mengapa kami tidak menggunakannya. Terima kasih telah mengklarifikasi, dan menegaskan kembali pikiran saya.
PseudoPsyche
1
Saya punya banyak masalah dengan bzeroimplementasi yang rusak . Pada array non-aligned, ini digunakan untuk melampaui panjang yang disediakan dan nol lebih banyak byte. Tidak pernah mengalami masalah seperti itu setelah beralih ke memset.
rustyx
Jangan lupa tentang memset_syang harus digunakan jika Anda ingin memastikan kompiler tidak diam-diam mengoptimalkan-jauh panggilan untuk "menggosok" memori untuk beberapa tujuan terkait keamanan (seperti mengosongkan wilayah memori yang berisi sensitif sepotong informasi seperti kata sandi teks-bersih).
Christopher Schultz
69

Saya kira Anda menggunakan (atau guru Anda dipengaruhi oleh) Pemrograman Jaringan UNIX oleh W. Richard Stevens. Ia bzerosering menggunakan alih-alih memset, bahkan dalam edisi paling baru. Buku ini sangat populer, saya pikir itu menjadi idiom dalam pemrograman jaringan yang mengapa Anda masih melihatnya digunakan.

Saya akan tetap dengan memsethanya karena bzerosudah usang dan mengurangi portabilitas. Saya ragu Anda akan melihat keuntungan nyata dari menggunakan satu di atas yang lain.

austin
sumber
4
Anda benar. Kami tidak memerlukan buku pelajaran untuk kursus ini, tetapi saya baru saja memeriksa silabusnya lagi dan Pemrograman Jaringan UNIX memang terdaftar sebagai sumber daya opsional. Terima kasih.
PseudoPsyche
9
Sebenarnya lebih buruk dari itu. Itu ditinggalkan di POSIX.1-2001 dan dihapus di POSIX.1-2008.
paxdiablo
9
Mengutip halaman 8 dari edisi ketiga Pemrograman Jaringan UNIX oleh W. Richard Stevens - Memang, penulis TCPv3 membuat kesalahan dengan menukar argumen kedua dan ketiga untuk memset dalam 10 kejadian pencetakan pertama. Kompiler AC tidak dapat menangkap kesalahan ini karena kedua kejadiannya sama ... itu adalah kesalahan, dan dapat dihindari menggunakan bzero, karena menukar dua argumen ke bzero akan selalu ditangkap oleh kompiler C jika fungsi prototipe digunakan. Namun seperti yang ditunjukkan paxdiablo, bzero sudah usang.
Aaron Newton
@ AaronNewton, Anda harus menambahkan itu ke jawaban Michael karena itu mengkonfirmasi apa yang dia katakan.
Synetech
52

Satu keuntungan yang saya pikir bzero()lebih dari memset()menetapkan memori ke nol adalah bahwa ada kemungkinan berkurangnya kesalahan.

Lebih dari sekali saya menemukan bug yang terlihat seperti:

memset(someobject, size_of_object, 0);    // clear object

Kompiler tidak akan mengeluh (walaupun mungkin menaikkan beberapa tingkat peringatan pada beberapa kompiler) dan efeknya adalah memori tidak dihapus. Karena ini tidak membuang objek - hanya membiarkannya saja - ada kemungkinan yang baik bahwa bug mungkin tidak terwujud menjadi sesuatu yang jelas.

Fakta yang bzero()tidak standar adalah iritasi kecil. (FWIW, saya tidak akan terkejut jika sebagian besar panggilan fungsi dalam program saya tidak standar; bahkan menulis fungsi semacam itu adalah jenis pekerjaan saya).

Dalam komentar untuk jawaban lain di sini, Aaron Newton mengutip yang berikut dari Pemrograman Jaringan Unix, Volume 1, Edisi ke-3 oleh Stevens, et al., Bagian 1.2 (penekanan ditambahkan):

bzerobukan fungsi ANSI C. Ini berasal dari kode jaringan Berkely awal. Namun demikian, kami menggunakannya di seluruh teks, alih-alih fungsi ANSI C memset, karena bzerolebih mudah diingat (dengan hanya dua argumen) daripada memset(dengan tiga argumen). Hampir setiap vendor yang mendukung API soket juga menyediakan bzero, dan jika tidak, kami memberikan definisi makro di unp.hheader kami .

Memang, penulis TCPv3 [TCP / IP Illustrated, Volume 3 - Stevens 1996] membuat kesalahan dengan menukar argumen kedua dan ketiga memsetdalam 10 kejadian dalam pencetakan pertama . Kompiler AC tidak dapat menangkap kesalahan ini karena kedua argumen memiliki tipe yang sama. (Sebenarnya, argumen kedua adalah intdan argumen ketiga adalah size_t, yang biasanya merupakan unsigned int, tetapi nilai yang ditentukan, masing-masing, 0 dan 16, masih dapat diterima untuk jenis argumen lainnya.) Panggilan untuk memsettetap berfungsi, karena hanya beberapa fungsi soket sebenarnya mengharuskan 8 byte terakhir dari struktur alamat soket Internet diatur ke 0. Namun demikian, itu adalah kesalahan, dan yang dapat dihindari dengan menggunakan bzero, karena menukar kedua argumen ke bzeroakan selalu ditangkap oleh kompiler C jika fungsi prototipe digunakan.

Saya juga percaya bahwa sebagian besar panggilan ke memset()nol memori, jadi mengapa tidak menggunakan API yang disesuaikan dengan use case itu?

Kelemahan yang mungkin untuk bzero()adalah bahwa kompiler mungkin lebih cenderung untuk mengoptimalkan memcpy()karena standar dan sehingga mereka mungkin ditulis untuk mengenalinya. Namun, perlu diingat bahwa kode yang benar masih lebih baik daripada kode yang salah yang telah dioptimalkan. Dalam kebanyakan kasus, menggunakan bzero()tidak akan menyebabkan dampak yang nyata pada kinerja program Anda, dan itu bzero()bisa menjadi fungsi makro atau inline yang diperluas memcpy().

Michael Burr
sumber
Ya, saya kira ini mungkin menjadi alasan ketika bekerja di ruang kelas seperti ini, sehingga membuatnya kurang membingungkan bagi para siswa. Saya tidak berpikir ini adalah masalah dengan profesor saya. Dia adalah guru RTFM yang sangat besar. Jika Anda memiliki pertanyaan yang dapat dijawab oleh manual, ia akan membuka halaman manual pada proyektor di kelas dan menunjukkannya kepada Anda. Dia sangat banyak tentang menanamkan ke dalam pikiran semua orang bahwa ada manual untuk dibaca dan menjawab sebagian besar pertanyaan Anda. Saya bersyukur untuk ini, yang bertentangan dengan beberapa profesor lainnya.
PseudoPsyche
5
Saya pikir ini adalah argumen yang dapat dibuat bahkan di luar kelas - saya telah melihat bug ini dalam kode produksi. Bagi saya itu adalah kesalahan mudah. Saya juga menebak bahwa sebagian besar memset()panggilan hanya untuk nol blok memori, yang saya pikir adalah argumen lain untuk bzero(). Apa arti huruf 'b' bzero()?
Michael Burr
7
+1. Itu memsetmelanggar pemesanan parameter umum "buffer, buffer_size" membuatnya IMO rawan kesalahan.
jamesdlin
Dalam Pascal mereka menghindarinya dengan menyebutnya "fillchar" dan dibutuhkan char. Kebanyakan kompiler C / C ++ akan mengambil yang itu. Yang membuat saya bertanya-tanya mengapa kompiler tidak mengatakan "Anda melewati pointer 32/64 bit di mana byte diharapkan" dan menendang Anda dengan kuat dalam kesalahan kompiler.
Móż
1
@Gewure argumen kedua dan ketiga salah urutan; panggilan fungsi yang dikutip tidak melakukan apa
Ichthyo
4

Ingin menyebutkan sesuatu tentang argumen bzero vs memset. Instal ltrace dan kemudian bandingkan apa yang dilakukannya di bawah tenda. Di Linux dengan libc6 (2.19-0ubuntu6.6), panggilan yang dibuat persis sama (via ltrace ./test123):

long m[] = {0}; // generates a call to memset(0x7fffefa28238, '\0', 8)
int* p;
bzero(&p, 4);   // generates a call to memset(0x7fffefa28230, '\0', 4)

Saya telah diberitahu bahwa kecuali saya bekerja di dalam libc yang dalam atau sejumlah antarmuka kernel / syscall, saya tidak perlu khawatir tentang mereka. Yang harus saya khawatirkan adalah bahwa panggilan memenuhi persyaratan zero'ing buffer. Yang lain telah menyebutkan tentang mana yang lebih disukai daripada yang lain jadi saya akan berhenti di sini.

permen karet
sumber
Ini terjadi karena beberapa versi GCC akan memancarkan kode memset(ptr, 0, n)ketika mereka melihat bzero(ptr, n)dan mereka tidak dapat mengubahnya menjadi kode inline.
zwol
@ zwol Sebenarnya ini adalah makro.
SS Anne
1
@SSAnne gcc 9.3 di komputer saya melakukan transformasi ini sendiri, tanpa bantuan dari makro di header sistem. extern void bzero(void *, size_t); void clear(void *p, size_t n) { bzero(p, n); }menghasilkan panggilan ke memset. (Sertakan stddef.huntuk size_ttanpa apa-apa lagi yang bisa mengganggu.)
zwol
4

Anda mungkin tidak boleh menggunakan bzero, itu sebenarnya bukan standar C, itu adalah hal POSIX.

Dan perhatikan bahwa kata "adalah" - kata itu sudah tidak digunakan lagi di POSIX.1-2001 dan dihapus di POSIX.1-2008 sebagai penghormatan untuk memset agar Anda lebih baik menggunakan fungsi standar C.

paxdiablo
sumber
Apa yang Anda maksud dengan standar C? Maksud Anda tidak ditemukan di perpustakaan C standar?
Koray Tugay
@ Koray, standar C berarti standar ISO dan, ya, bzerobukan bagian dari itu.
paxdiablo
Tidak maksud saya, saya tidak tahu apa yang Anda maksud dengan standar apa pun. Apakah standar ISO berarti perpustakaan C standar? Itu datang dengan bahasa? Perpustakaan minimal yang kita tahu akan ada di sana?
Koray Tugay
2
@ Koray, ISO adalah organisasi standar yang bertanggung jawab atas standar C, yang saat ini adalah C11, dan yang sebelumnya C99 dan C89. Mereka menetapkan aturan yang harus diikuti oleh implementasi agar dapat dipertimbangkan C. Jadi ya, jika standar mengatakan implementasi harus menyediakan memset, itu akan ada untuk Anda. Kalau tidak, itu bukan C.
paxdiablo
2

Untuk fungsi memset, argumen kedua adalah intdan argumen ketiga adalah size_t,

void *memset(void *s, int c, size_t n);

yang biasanya merupakan unsigned int, tetapi jika nilai-nilai seperti, 0 and 16untuk argumen kedua dan ketiga masing-masing dimasukkan dalam urutan yang salah seperti 16 dan 0 maka, panggilan untuk memset tersebut masih dapat berfungsi, tetapi tidak akan melakukan apa-apa. Karena jumlah byte yang diinisialisasi ditentukan sebagai 0.

void bzero(void *s, size_t n)

Kesalahan seperti itu dapat dihindari dengan menggunakan bzero, karena menukar dua argumen ke bzero akan selalu ditangkap oleh kompiler C jika fungsi prototipe digunakan.

Havish
sumber
1
Kesalahan semacam itu juga dapat dihindari dengan memset jika Anda hanya menganggap panggilan itu sebagai "set memori ini ke nilai ini untuk ukuran ini", atau jika Anda memiliki IDE yang memberi Anda prototipe atau bahkan jika Anda hanya tahu apa yang Anda melakukan :-)
paxdiablo
Setuju, tetapi fungsi ini dibuat pada saat IDE cerdas tersebut tidak tersedia untuk dukungan.
havish
2

Singkatnya: memset membutuhkan lebih banyak operasi perakitan bzero.

Ini adalah sumbernya: http://fdiv.net/2009/01/14/memset-vs-bzero-ultimate-showdown

Tal Bar
sumber
Ya, itu satu hal yang saya sebutkan di OP. Saya sebenarnya bahkan terhubung ke halaman itu. Ternyata itu tampaknya tidak membuat banyak perbedaan karena beberapa optimisasi kompiler. Untuk lebih jelasnya lihat jawaban yang diterima oleh ouah.
PseudoPsyche
6
Ini hanya menunjukkan bahwa satu implementasi sampah memset lambat. Pada MacOS X dan beberapa sistem lainnya, memset menggunakan kode yang diatur pada saat booting tergantung pada prosesor yang Anda gunakan, memanfaatkan register vektor secara penuh, dan untuk ukuran besar ia menggunakan instruksi prefetch dengan cara cerdas untuk mendapatkan bit terakhir kecepatan.
gnasher729
lebih sedikit instruksi tidak berarti eksekusi lebih cepat. Sebenarnya optimasi sering meningkatkan ukuran biner dan jumlah instruksi karena loop terbuka, fungsi inlining, penyelarasan loop ... Lihatlah kode optimal yang layak dan Anda akan melihatnya sering memiliki lebih banyak instruksi daripada implementasi buruk
phuclv
2

Silakan sesuka Anda. :-)

#ifndef bzero
#define bzero(d,n) memset((d),0,(n))
#endif

Perhatikan bahwa:

  1. Asli bzerotidak mengembalikan apa-apa, memsetmengembalikan penunjuk kosong ( d). Ini dapat diperbaiki dengan menambahkan typecast untuk membatalkan dalam definisi.
  2. #ifndef bzerotidak mencegah Anda menyembunyikan fungsi asli bahkan jika itu ada. Ini menguji keberadaan makro. Ini dapat menyebabkan banyak kebingungan.
  3. Tidak mungkin membuat pointer fungsi ke makro. Saat menggunakan bzeromelalui pointer fungsi, ini tidak akan berfungsi.
Bruce
sumber
1
Ada apa dengan ini, @Leeor? Antipati umum untuk makro? Atau Anda tidak menyukai fakta bahwa makro ini dapat dikacaukan dengan fungsinya (dan bahkan mungkin menyembunyikannya)?
Palec
1
@Palec, yang terakhir. Menyembunyikan redefinisi sebagai makro dapat menyebabkan banyak kebingungan. Programmer lain yang menggunakan kode ini berpikir dia menggunakan satu hal, dan tanpa sadar dipaksa untuk menggunakan yang lain. Itu bom waktu.
Leeor
1
Setelah memikirkannya lagi, saya setuju bahwa ini memang solusi yang buruk. Di antara hal-hal lain saya menemukan alasan teknis: Ketika menggunakan bzerovia pointer fungsi, ini tidak akan berfungsi.
Palec
Anda benar-benar harus memanggil makro Anda sesuatu selain bzero. Ini adalah kekejaman.
Dan Bechard
-2

memset mengambil 3 parameter, bzero mengambil 2 dalam memori dibatasi bahwa parameter tambahan akan mengambil 4 byte lebih dan sebagian besar waktu itu akan digunakan untuk mengatur semuanya menjadi 0

Skynight
sumber