Зачем использовать bzero над memset?

156

В классе системного программирования, который я взял в предыдущем семестре, мы должны были реализовать базовый клиент / сервер на C. При инициализации sock_addr_inбуферов struct, like или char (которые мы использовали для отправки данных между клиентом и сервером и обратно), профессор поручил нам только использовать, bzeroа не memsetинициализировать их. Он никогда не объяснял почему, и мне любопытно, есть ли на то веские причины для этого?

Я вижу здесь: http://fdiv.net/2009/01/14/memset-vs-bzero-ultimate-showdown, который bzeroболее эффективен из-за того факта, что когда-либо будет только обнуление памяти, поэтому он не должны сделать любую дополнительную проверку, которая memsetможет сделать. Это, тем не менее, не обязательно является причиной для того, чтобы вообще не использовать memsetдля обнуления памяти.

bzeroсчитается устаревшим и, кроме того, не является стандартной функцией C. Согласно руководству, memsetявляется предпочтительным по bzeroэтой причине. Так почему же вы хотите по - прежнему использовать bzeroболее memset? Просто для повышения эффективности, или это нечто большее? Кроме того, каковы преимущества memsetнад bzeroчто делает его де - факто предпочтительным вариантом для новых программ?

PseudoPsyche
источник
28
"Зачем использовать bzero над memset?" - Не надо. Memset стандартная, bzero нет.
30
bzero - это BSDism (). memset () - это ANSI-C. в настоящее время bzero (), вероятно, будет реализован как макрос. Попросите своего профессора побриться и почитать несколько книг. эффективность является фиктивным аргументом. Системный вызов или переключение контекста могут легко стоить десятки тысяч тактов, один проход по буферу выполняется на скорости шины. Если вы хотите оптимизировать сетевые программы: минимизируйте количество системных вызовов (путем чтения / записи больших кусков)
wildplasser
7
Идея, которая memsetможет быть несколько менее эффективной из-за «немного большего количества проверок», определенно является случаем преждевременной оптимизации: какие бы выгоды вы ни увидели от пропуска одной или двух инструкций ЦП, они не стоят того, когда вы можете поставить под угрозу переносимость вашего код. bzeroустарел, и этого достаточно, чтобы не использовать его.
dasblinkenlight
4
Часто вы можете вместо этого добавить инициализатор `= {0}` и вообще не вызывать функцию. Это стало легче, когда на рубеже веков C перестал требовать предварительного объявления локальных переменных. Некоторые действительно старые бумажные изделия все еще застряли глубоко в прошлом веке.
MSalters
1
@SSAnne нет, но, скорее всего, оно произошло из рекомендованной книги для курса, на который он оказал влияние, как упомянуто в одном из ответов ниже: stackoverflow.com/a/17097072/1428743
PseudoPsyche

Ответы:

152

Я не вижу никаких причин предпочитать bzeroболее memset.

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

Теперь, что касается эффективности, компиляторы gccиспользуют встроенные реализации, для memsetкоторых переключаются на конкретную реализацию при обнаружении константы 0. То же самое, glibcкогда встроенные функции отключены.

ouah
источник
Спасибо. Это имеет смысл. Я был почти уверен, что это memsetвсегда следует использовать в этом случае, но был озадачен тем, почему мы его не использовали. Спасибо за разъяснение и подтверждение моих мыслей.
ПсевдоПсихе
1
У меня было много проблем со сломанными bzeroреализациями. На невыровненных массивах он используется для превышения заданной длины и обнуления еще нескольких байтов. Никогда не было такой проблемы после перехода на memset.
rustyx
Не забывайте о том, memset_sчто следует использовать, если вы хотите убедиться, что компилятор не выполняет тихую оптимизацию вызова «очистки» памяти для каких-либо целей, связанных с безопасностью (например, для удаления области памяти, содержащей конфиденциальную информацию). часть информации, такая как пароль в виде открытого текста).
Кристофер Шульц
69

Я предполагаю, что вы использовали (или под влиянием вашего учителя) сетевое программирование UNIX У. Ричарда Стивенса. Он bzeroчасто использует вместо memset, даже в самом современном издании. Книга настолько популярна, что я думаю, что она стала идиомой в сетевом программировании, поэтому вы до сих пор ее используете.

Я бы придерживался memsetпросто потому, что bzeroустарел и снижает мобильность. Я сомневаюсь, что вы увидите какие-либо реальные выгоды от использования одного над другим.

Остин
источник
4
Вы были бы правы. Для этого курса у нас не было учебников, но я просто проверил учебный план, и сетевое программирование в UNIX действительно указано в качестве дополнительного ресурса. Спасибо.
ПсевдоПсихе
9
Это на самом деле хуже, чем это. Это было объявлено устаревшим в POSIX.1-2001 и удалено в POSIX.1-2008.
paxdiablo
9
Цитирую страницу 8 третьего издания сетевого программирования UNIX У. Ричарда Стивенса - действительно, автор TCPv3 допустил ошибку, поменяв местами второй и третий аргументы в memset в 10 экземплярах первой печати. Компилятор AC не может перехватить эту ошибку, потому что оба вхождения одинаковы ... это была ошибка, и ее можно было бы избежать с помощью bzero, потому что замена двух аргументов на bzero всегда будет перехватываться компилятором C, если используются прототипы функций. Однако, как указал Паксдиабло, bzero устарела.
Аарон Ньютон
@AaronNewton, ты должен добавить это к ответу Майкла, так как он подтверждает то, что он сказал.
Synetech
52

Единственное преимущество, которое, я думаю, bzero()имеет преимущество memset()при установке памяти на ноль, заключается в том, что вероятность ошибки может быть уменьшена.

Я не раз сталкивался с ошибкой, которая выглядела так:

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

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

Тот факт, что bzero()не является стандартным, является незначительным раздражителем. (FWIW, я не удивлюсь, если большинство вызовов функций в моих программах будут нестандартными; на самом деле написание таких функций является моей работой).

В комментарии к другому ответу здесь Аарон Ньютон процитировал следующее из Сетевого программирования Unix, Том 1, 3-е издание, Стивенс и др., Раздел 1.2 (выделение добавлено):

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

Действительно, автор TCPv3 [Иллюстрированный TCP / IP, том 3 - Стивенс, 1996] допустил ошибку, поменяв местами второй и третий аргументы memsetв 10 случаях в первой печати . Компилятор AC не может перехватить эту ошибку, потому что оба аргумента имеют одинаковый тип. (На самом деле, вторым аргументом является, intа третьим аргументом size_t, который обычно является unsigned int, но указанные значения, 0 и 16, соответственно, все еще приемлемы для другого типа аргумента.) Вызов memsetвсе еще работает, потому что только немногие функции сокетов фактически требуют, чтобы последние 8 байтов структуры адреса интернет-сокета были равны 0. Тем не менее, это была ошибка, и ее можно избежать, используя bzeroпотому что замена двух аргументов на bzeroвсегда будет перехватываться компилятором C, если используются прототипы функций.

Я также считаю, что подавляющее большинство обращений к memset()нулю памяти, так почему бы не использовать API, который приспособлен для этого случая использования?

Возможным недостатком bzero()является то, что компиляторы могут с большей вероятностью оптимизировать, memcpy()потому что это стандарт, и поэтому они могут быть написаны для его распознавания. Однако имейте в виду, что правильный код все же лучше, чем неправильный код, который был оптимизирован. В большинстве случаев использование bzero()не окажет заметного влияния на производительность вашей программы, и это bzero()может быть макро или встроенная функция, которая расширяется до memcpy().

Майкл Берр
источник
Да, я полагаю, что это может быть аргументом при работе в классной комнате, как это было, чтобы сделать это потенциально менее запутанным для студентов. Однако я не думаю, что так было с моим профессором. Он был очень большим учителем RTFM. Если у вас есть вопрос, на который может ответить руководство, он откроет справочные страницы на проекторе в классе и покажет вам. Он очень хотел, чтобы у всех на уме появилось руководство для чтения, и он отвечает на большинство ваших вопросов. Я благодарен за это, в отличие от некоторых других профессоров.
ПсевдоПсихе
5
Я думаю, что это аргумент, который можно привести даже за пределами классной комнаты - я видел эту ошибку в рабочем коде. Это кажется мне простой ошибкой. Я также предположил бы, что подавляющее большинство memset()вызовов просто обнуляют блок памяти, что я считаю еще одним аргументом bzero(). Что означает «b» в bzero()любом случае?
Майкл Берр
7
+1. Это memsetнарушает общий порядок параметров "buffer, buffer_size", что делает его особенно подверженным ошибкам IMO.
Джеймсдлин
В Паскале они избегают этого, называя его «fillchar», и он принимает символ. Большинство компиляторов C / C ++ подберут это. Это заставляет меня задаться вопросом, почему компиляторы не говорят «вы передаете 32/64-битный указатель, где ожидается байт» и твердо пинаете вас в ошибках компилятора.
2013 г.
1
@ Второй и третий аргументы должны быть в неправильном порядке; цитируемый вызов функции ничего не делает
Ichthyo
4

Хотел бы упомянуть кое-что о аргументе 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 или какого-либо другого интерфейса ядра / системного вызова, мне не о чем беспокоиться. Все, о чем я должен беспокоиться, это то, что вызов удовлетворяет требованию обнуления буфера. Другие упоминали о том, какой из них предпочтительнее другого, поэтому я остановлюсь здесь.

gumchew
источник
Это происходит потому, что некоторые версии GCC генерируют код, memset(ptr, 0, n)когда они видят, bzero(ptr, n)и не могут преобразовать его во встроенный код.
Звол
@zwol Это на самом деле макрос.
SS Anne
1
@SSAnne gcc 9.3 на моем компьютере выполняет это преобразование самостоятельно, без помощи макросов в системных заголовках. extern void bzero(void *, size_t); void clear(void *p, size_t n) { bzero(p, n); }производит вызов memset. (Включите stddef.hдля size_tбез чего - либо еще , что могло помешать.)
zwol
4

Вы, вероятно, не должны использовать bzero, это на самом деле не стандартный C, это была вещь POSIX.

И обратите внимание, что слово «было» - оно устарело в POSIX.1-2001 и удалено в POSIX.1-2008 из-за memset, поэтому вам лучше использовать стандартную функцию C.

paxdiablo
источник
Что вы подразумеваете под стандартом C? Вы имеете в виду, что он не найден в стандартной библиотеке C?
Корай Тугай
@ Koray, стандарт C означает стандарт ISO и, да, bzeroне является частью этого.
paxdiablo
Нет, я имею в виду, я не знаю, что вы подразумеваете под любым стандартом. Означает ли стандарт ISO стандартную библиотеку C? Что приходит с языком? Минимальная библиотека, которую мы знаем, будет там?
Корай Тугай
2
@Koray, ISO - это организация по стандартизации, которая отвечает за стандарт C, текущий - C11, а более ранние - C99 и C89. Они устанавливают правила, которым должна следовать реализация, чтобы считаться C. Так что, если стандарт говорит, что реализация должна предоставлять memset, она будет там для вас. В противном случае, это не C.
paxdiablo
2

Для 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, если используются прототипы функций.

havish
источник
1
Такой ошибки также можно избежать с помощью memset, если вы просто думаете о вызове как об «установке этой памяти на это значение для этого размера», или если у вас есть IDE, которая дает вам прототип, или даже если вы просто знаете, что вы делаю :-)
paxdiablo
Согласитесь, но эта функция была создана в то время, когда такие интеллектуальные IDE были недоступны для поддержки.
havish
2

Короче говоря: memset требуется больше сборочных операций, чем bzero.

Это источник: http://fdiv.net/2009/01/14/memset-vs-bzero-ultimate-showdown

Тал Бар
источник
Да, это одна вещь, которую я упомянул в ОП. Я даже ссылался на эту страницу. Оказывается, это не имеет большого значения из-за некоторых оптимизаций компилятора. Для более подробной информации смотрите принятый ответ от ouah.
ПсевдоПсихе
6
Это только показывает, что одна реализация мусмета медленная. В MacOS X и некоторых других системах memset использует код, который устанавливается во время загрузки в зависимости от используемого вами процессора, в полной мере использует векторные регистры, а для больших размеров он использует инструкции предварительной выборки умными способами, чтобы получить последний бит скорости.
gnasher729
меньшее количество инструкций не означает более быстрое выполнение. Фактически, оптимизации часто увеличивают двоичный размер и количество команд из-за развертывания цикла, вставки функции, выравнивания цикла ... Посмотрите на любой приличный оптимизированный код, и вы увидите, что он часто содержит гораздо больше инструкций, чем дерьмовые реализации
phuclv
2

Имейте это так, как вам нравится. :-)

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

Обратите внимание, что:

  1. Оригинал bzeroничего не возвращает, memsetвозвращает void pointer ( d). Это можно исправить, добавив типовое значение void в определение.
  2. #ifndef bzeroне мешает вам скрыть оригинальную функцию, даже если она существует. Он проверяет наличие макроса. Это может вызвать много путаницы.
  3. Невозможно создать указатель на функцию макроса. При использовании bzeroуказателей функций это не будет работать.
Брюс
источник
1
В чем проблема, @Leeor? Общая антипатия к макросам? Или вам не нравится тот факт, что этот макрос может быть перепутан с функцией (и, возможно, даже скрывает ее)?
Палек
1
@Palec, последний. Сокрытие переопределения в качестве макроса может привести к путанице. Другой программист, использующий этот код, думает, что он использует одну вещь, и неосознанно вынужден использовать другую. Это бомба замедленного действия.
Leeor
1
Подумав еще раз, я согласен, что это действительно плохое решение. Среди прочего я нашел техническую причину: при использовании bzeroуказателей функций это не будет работать.
Палек
Вы действительно должны были назвать свой макрос как-то иначе bzero. Это злодеяние.
Дэн Бешард
-2

memset принимает 3 параметра, bzero занимает 2 в ограниченном объеме памяти, что дополнительный параметр будет занимать еще 4 байта, и большую часть времени он будет использоваться для установки всего на 0

Skynight
источник