Защо да използвате bzero над memset?

В клас по системно програмиране, който взех този предишен семестър, трябваше да внедрим основен клиент/сървър в C. При инициализиране на структурите, като sock_addr_in, или символни буфери (които използвахме за изпращане на данни напред и назад между клиент и сървър) професорът ни инструктира да използваме само bzero, а не memset, за да ги инициализираме. Той така и не обясни защо и ми е интересно дали има основателна причина за това?

Виждам тук: http://fdiv.net/2009/01/14/memset-vs-bzero-ultimate-showdown, че bzero е по-ефективен поради факта, че винаги ще нулира паметта, така че не е необходимо да прави допълнителни проверки, които memset може да прави . Това все още не изглежда непременно като причина да не използвате абсолютно memset за нулиране на паметта.

bzero се счита за остаряла и освен това не е стандартна C функция. Според ръководството memset се предпочита пред bzero поради тази причина. Така че защо искате да използвате bzero над memset? Само за повишаване на ефективността или е нещо повече? По същия начин какви са предимствата на memset пред bzero, които го правят де факто предпочитаната опция за по-нови програми?


person PseudoPsyche    schedule 13.06.2013    source източник
comment
изглежда, че memset е по-преносим   -  person tay10r    schedule 14.06.2013
comment
Защо да използвате bzero над memset? - Недей. Memset е стандартен, bzero не е.   -  person    schedule 14.06.2013
comment
въпросът ми е - защо не използвам calloc в този случай?   -  person EkoostikMartin    schedule 14.06.2013
comment
bzero е BSDism(). memset() е ansi-c. в наши дни bzero() вероятно ще бъде имплементиран като макрос. Помолете вашия професор да се обръсне и да прочете някои книги. ефективността е фалшив аргумент. Системно повикване или контекстно превключване може лесно да струва десетки хиляди такта, едно преминаване през буфер работи със скорост на шината. Ако искате да оптимизирате мрежовите програми: минимизирайте броя на системните извиквания (чрез четене/запис на по-големи парчета)   -  person wildplasser    schedule 14.06.2013
comment
Идеята, че memset може да е малко по-малко ефективен поради малко повече проверки, определено е случай на преждевременна оптимизация: каквито и да са ползите, които можете да видите от пропускането на една или две инструкции на процесора, не си заслужават, когато можете да застрашите преносимостта на вашия код . bzero е остарял и това е достатъчна причина да не го използвате.   -  person Sergey Kalinichenko    schedule 14.06.2013
comment
Свързани: bzero() & bcopy() срещу memset() & memcpy()   -  person Palec    schedule 11.01.2015
comment
Често вместо това можете да добавите инициализатор ` = {0}` и изобщо да не извиквате функция. Това стана по-лесно, когато в края на века C спря да изисква предварително деклариране на локални променливи. Някои наистина стари хартиени съдове все още са затънали дълбоко в предишния век.   -  person MSalters    schedule 12.10.2015
comment
Твоят професор някога давал ли ти е причина?   -  person S.S. Anne    schedule 15.04.2020
comment
@S.S.Anne не, но най-вероятно произлиза от препоръчана книга за курса, от който е бил повлиян, както е споменато в един от отговорите по-долу: stackoverflow.com/a/17097072/1428743   -  person PseudoPsyche    schedule 17.04.2020


Отговори (9)


Не виждам причина да предпочитам bzero пред memset.

memset е стандартна C функция, докато bzero никога не е била C стандартна функция. Обосновката вероятно е, защото можете да постигнете точно същата функционалност, като използвате функцията memset.

Що се отнася до ефективността, компилатори като gcc използват вградени реализации за memset, които превключват към конкретна реализация, когато бъде открита константа 0. Същото за glibc, когато вградените са деактивирани.

person ouah    schedule 13.06.2013
comment
Благодаря. Това има смисъл. Бях почти сигурен, че memset винаги трябва да се използва в този случай, но бях объркан защо не го използваме. Благодаря за пояснението и за потвърждаване на моите мисли. - person PseudoPsyche; 14.06.2013
comment
Имах много проблеми със счупени bzero реализации. При неподравнени масиви той превишава предоставената дължина и нулира малко повече байтове. Никога не съм имал такъв проблем след преминаване към memset. - person rustyx; 21.08.2014
comment
Не забравяйте за memset_s, който трябва да се използва, ако искате да сте сигурни, че компилаторът няма да оптимизира тихо извикване за почистване на паметта за някаква цел, свързана със сигурността (като изчистване на област от паметта, която съдържа чувствителна част на информация като парола с ясен текст). - person Christopher Schultz; 13.01.2018

Предполагам, че сте използвали (или вашият учител е бил повлиян от) UNIX Network Programming от W. Richard Stevens. Той използва bzero често вместо memset, дори и в най-актуалното издание. Книгата е толкова популярна, мисля, че се превърна в идиом в мрежовото програмиране, поради което все още я виждате използвана.

Бих се придържал към memset просто защото bzero е отхвърлен и намалява преносимостта. Съмнявам се, че ще видите някакви реални ползи от използването на един над друг.

person austin    schedule 13.06.2013
comment
Вие ще бъдете прави. Нямахме необходимите учебници за този курс, но току-що проверих отново учебната програма и Мрежовото програмиране на UNIX наистина е посочено като незадължителен ресурс. Благодаря. - person PseudoPsyche; 14.06.2013
comment
Всъщност е по-лошо от това. Беше отхвърлен в POSIX.1-2001 и премахнат в POSIX.1-2008. - person paxdiablo; 20.08.2013
comment
Цитирайки страница 8 от третото издание на UNIX Network Programming от W. Richard Stevens - Наистина, авторът на TCPv3 направи грешката да размени втория и третия аргумент на memset в 10 срещания на първи печат. C компилаторът не може да улови тази грешка, защото и двете събития са еднакви... това беше грешка и можеше да бъде избегната чрез bzero, тъй като размяната на двата аргумента с bzero винаги ще бъде уловена от C компилатора, ако се използват прототипи на функции. Въпреки това, както посочи paxdiablo, bzero е отхвърлен. - person Aaron Newton; 25.07.2015
comment
@AaronNewton, трябва да добавите това към отговора на Майкъл, тъй като потвърждава казаното от него. - person Synetech; 11.09.2015

Единственото предимство, което мисля, че bzero() има пред memset() за настройка на паметта на нула, е, че има намален шанс да бъде направена грешка.

Повече от веднъж съм попадал на грешка, която изглеждаше така:

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

Компилаторът няма да се оплаче (въпреки че може би вдигането на някои нива на предупреждение може да се случи при някои компилатори) и ефектът ще бъде, че паметта не се изчиства. Тъй като това не изхвърля обекта в боклук - просто го оставя сам - има приличен шанс грешката да не се прояви в нещо очевидно.

Фактът, че bzero() не е стандартен, е малък дразнител. (FWIW, не бих се изненадал, ако повечето извиквания на функции в моите програми са нестандартни; всъщност писането на такива функции е нещо като моя работа).

В коментар към друг отговор тук, Арън Нютон цитира следното от Unix Network Programming, Том 1, 3-то издание от Stevens, et al., Раздел 1.2 (курсивът е добавен):

bzero не е ANSI C функция. Произлиза от ранния мрежов код на Berkely. Въпреки това, ние го използваме в целия текст, вместо функцията ANSI C memset, тъй като bzero се помни по-лесно (само с два аргумента), отколкото memset (с три аргумента). Почти всеки доставчик, който поддържа API за гнезда, също предоставя bzero, а ако не, ние предоставяме дефиниция на макрос в нашата заглавка unp.h.

Наистина, авторът на TCPv3 [TCP/IP Illustrated, Volume 3 - Stevens 1996] направи грешката да размени втория и третия аргумент на memset в 10 пъти при първото отпечатване. C компилаторът не може да улови тази грешка, защото и двата аргумента са от един и същи тип. (Всъщност вторият аргумент е int, а третият аргумент е size_t, което обикновено е unsigned int, но посочените стойности, съответно 0 и 16, все още са приемливи за другия тип аргумент.) Извикването на memset все още работи , тъй като само няколко от функциите на сокета всъщност изискват последните 8 байта от адресна структура на интернет сокет да бъдат зададени на 0. Независимо от това, това беше грешка и такава, която можеше да бъде избегната чрез използване на bzero, тъй като размяната на двата аргумента на bzero винаги ще бъде уловен от C компилатора, ако се използват прототипи на функции.

Също така вярвам, че по-голямата част от обажданията към memset() са до нулева памет, така че защо да не използвате API, който е пригоден за този случай на употреба?

Възможен недостатък на bzero() е, че компилаторите може да са по-склонни да оптимизират memcpy(), защото е стандартен и затова може да са написани да го разпознават. Имайте предвид обаче, че правилният код все още е по-добър от неправилния код, който е оптимизиран. В повечето случаи използването на bzero() няма да причини забележимо въздействие върху производителността на вашата програма и това bzero() може да бъде макрос или вградена функция, която се разширява до memcpy().

person Michael Burr    schedule 13.06.2013
comment
Да, предполагам, че това може да е разсъждение, когато работите в обстановка в класна стая като тази, така че да бъде потенциално по-малко объркващо за учениците. Не мисля обаче, че това беше случаят с моя професор. Той беше много голям учител по RTFM. Ако имате въпрос, на който можете да отговорите в ръководството, той ще извади страниците с ръководство на проектора в клас и ще ви покаже. Той много искаше да втълпи в съзнанието на всички, че ръководството е за четене и отговаря на повечето от вашите въпроси. Аз съм благодарен за това, за разлика от някои други професори. - person PseudoPsyche; 14.06.2013
comment
Мисля, че това е аргумент, който може да се направи дори извън класната стая - виждал съм този бъг в производствения код. Струва ми се, че грешката е лесна за допускане. Бих предположил също, че по-голямата част от memset() извикванията са просто за нулиране на блок памет, което според мен е друг аргумент за bzero(). Какво изобщо означава "b" в bzero()? - person Michael Burr; 14.06.2013
comment
+1. Това memset нарушава общото подреждане на параметрите на буфера, buffer_size го прави особено податлив на грешки IMO. - person jamesdlin; 20.08.2013
comment
В Pascal те избягват това, като го наричат ​​fillchar и отнема char. Повечето C/C++ компилатори биха избрали този. Което ме кара да се чудя защо компилаторите не казват, че предавате 32/64 битов указател, където се очаква байт, и не ви изритват здраво в грешките на компилатора. - person Móż; 30.10.2013
comment
Предполагам, че b в bzero идва от „буфер“, @MichaelBurr. Но може да бъде и „двоичен“ или „байт“. - person Palec; 29.04.2015
comment
Изглежда, че това наистина е правилният отговор на въпроса. - person Synetech; 11.09.2015
comment
@MichaelBurr каква е грешката в примера за memset, който сте предоставили..? можеш ли да го обясниш? - person Gewure; 23.03.2017
comment
@Gewure вторият и третият аргумент са в грешен ред; цитираното извикване на функция не прави точно нищо - person Ichthyo; 20.04.2018

Исках да спомена нещо за аргумента bzero срещу memset. Инсталирайте ltrace и след това сравнете какво прави под капака. На Linux с libc6 (2.19-0ubuntu6.6), направените повиквания са абсолютно същите (чрез 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)

Казаха ми, че освен ако не работя в дълбоките недра на libc или който и да е интерфейс на ядро/системно повикване, не трябва да се тревожа за тях. Всичко, за което трябва да се тревожа, е обаждането да отговаря на изискването за нулиране на буфера. Други споменаха кой е за предпочитане пред другия, така че ще спра до тук.

person gumchew    schedule 22.04.2015
comment
Това се случва, защото някои версии на GCC ще излъчват код за memset(ptr, 0, n), когато видят bzero(ptr, n) и не могат да го конвертират във вграден код. - person zwol; 23.05.2018
comment
@zwol Това всъщност е макрос. - person S.S. Anne; 15.04.2020
comment
@S.S.Anne gcc 9.3 на моя компютър прави тази трансформация сама, без никаква помощ от макроси в системните заглавки. extern void bzero(void *, size_t); void clear(void *p, size_t n) { bzero(p, n); } произвежда повикване към memset. (Включете stddef.h за size_t без нищо друго, което би могло да попречи.) - person zwol; 15.04.2020

Вероятно не трябва да използвате bzero, това всъщност не е стандарт C, това беше POSIX нещо.

И имайте предвид, че думата "беше" - тя беше отхвърлена в POSIX.1-2001 и премахната в POSIX.1-2008 в уважение към memset, така че е по-добре да използвате стандартната C функция.

person paxdiablo    schedule 20.08.2013
comment
Какво имаш предвид под стандарт C? Искате да кажете, че не се намира в стандартната C библиотека? - person Koray Tugay; 28.04.2015
comment
@Koray, стандарт C означава стандарт ISO и, да, bzero не е част от него. - person paxdiablo; 28.04.2015
comment
Не, имам предвид, не знам какво имаш предвид по който и да е стандарт. Стандартът ISO означава ли стандартната C библиотека? Това идва с езика? Минималната библиотека, която знаем, че ще бъде там? - person Koray Tugay; 28.04.2015
comment
@Koray, ISO е организацията по стандартизация, която отговаря за стандарта C, като настоящият е C11, а по-ранните C99 и C89. Те определят правилата, които една реализация трябва да следва, за да се счита за C. Така че, да, ако стандартът казва, че една реализация трябва да предоставя memset, тя ще бъде там за вас. Иначе не е С. - person paxdiablo; 29.04.2015

За функцията memset вторият аргумент е int, а третият аргумент е size_t,

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

което обикновено е unsigned int, но ако стойностите като 0 and 16 съответно за втори и трети аргумент са въведени в грешен ред като 16 и 0, тогава такова извикване на memset все още може да работи, но няма да направи нищо. Тъй като броят байтове за инициализиране са посочени като 0.

void bzero(void *s, size_t n)

Такава грешка може да бъде избегната чрез използване на bzero, тъй като размяната на двата аргумента с bzero винаги ще бъде уловена от C компилатора, ако се използват прототипи на функции.

person havish    schedule 23.12.2013
comment
Такава грешка може също да бъде избегната с memset, ако просто мислите за повикването като задаване на тази памет на тази стойност за този размер, или ако имате IDE, която ви дава прототипа, или дори ако просто знаете какво правите: -) - person paxdiablo; 29.04.2015
comment
Съгласен съм, но тази функция беше създадена по времето, когато такива интелигентни IDE не бяха налични за поддръжка. - person havish; 25.07.2015

Накратко: memset изискват повече операции по сглобяване от bzero.

Това е източникът: http://fdiv.net/2009/01/14/memset-vs-bzero-ultimate-showdown

person Tal Bar    schedule 16.01.2014
comment
Да, това е едно нещо, което споменах в ОП. Всъщност дори направих връзка към точно тази страница. Оказва се, че това наистина не прави голяма разлика поради някои оптимизации на компилатора. За повече подробности вижте приетия отговор от ouah. - person PseudoPsyche; 18.01.2014
comment
Това само показва, че едно безсмислено внедряване на memset е бавно. В MacOS X и някои други системи memset използва код, който се настройва по време на зареждане в зависимост от процесора, който използвате, използва пълноценно векторните регистри и за големи размери използва инструкции за предварително извличане по интелигентни начини, за да получи последния бит на скоростта. - person gnasher729; 30.04.2015
comment
по-малко инструкции не означава по-бързо изпълнение. Всъщност оптимизациите често увеличават двоичния размер и броя на инструкциите поради разгръщане на цикъл, вграждане на функции, подравняване на цикъл... Погледнете всеки приличен оптимизиран код и ще видите, че често има много повече инструкции, отколкото скапани реализации - person phuclv; 22.11.2019

Имайте го както искате. :-)

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

Забележи, че:

  1. Оригиналът bzero не връща нищо, memset връща празен указател (d). Това може да се коригира чрез добавяне на typecast към void в дефиницията.
  2. #ifndef bzero не ви пречи да скриете оригиналната функция, дори ако тя съществува. Той тества съществуването на макрос. Това може да причини много объркване.
  3. Невъзможно е да се създаде функционален указател към макрос. Когато използвате bzero чрез функционални указатели, това няма да работи.
person Bruce    schedule 20.03.2014
comment
Какъв е проблемът с това, @Leeor? Обща антипатия към макросите? Или не харесвате факта, че този макрос може да бъде объркан с функцията (и вероятно дори да я скрива)? - person Palec; 29.04.2015
comment
@Palec, последното. Скриването на предефиниране като макрос може да доведе до толкова много объркване. Друг програмист, използващ този код, си мисли, че използва едно нещо и несъзнателно е принуден да използва другото. Това е бомба със закъснител. - person Leeor; 29.04.2015
comment
След като помислих още веднъж, се съгласявам, че това наистина е лошо решение. Между другото намерих техническа причина: Когато използвате bzero чрез указатели на функции, това няма да работи. - person Palec; 30.04.2015
comment
Наистина трябваше да наречете макроса си нещо различно от bzero. Това е зверство. - person Dan Bechard; 02.04.2018

memset приема 3 параметъра, bzero взема 2 в паметта, като този допълнителен параметър ще отнеме още 4 байта и през повечето време ще се използва за задаване на всичко на 0

person Skynight    schedule 23.07.2019