¿Por qué usar bzero sobre memset?

156

En una clase de Programación de Sistemas que tomé este semestre anterior, tuvimos que implementar un cliente / servidor básico en C. Al inicializar las estructuras, me gusta sock_addr_ino buffers de char (que solíamos enviar datos de un lado a otro entre el cliente y el servidor), el profesor nos indicó que solo los usemos bzeroy no los memsetinicialicemos. Nunca explicó por qué, y tengo curiosidad si hay una razón válida para esto.

Veo aquí: http://fdiv.net/2009/01/14/memset-vs-bzero-ultimate-showdown que bzeroes más eficiente debido al hecho de que solo habrá cero memoria, por lo que no tiene que hacer cualquier verificación adicional que memsetpueda hacer. Sin embargo, eso no necesariamente parece ser una razón para no usar absolutamente memsetpara poner a cero la memoria.

bzerose considera obsoleto y, además, no es una función estándar de C. De acuerdo con el manual, memsetse prefiere bzeropor esta razón. Así que ¿por qué desea utilizar aún bzeromás memset? ¿Solo por las ganancias de eficiencia, o es algo más? Del mismo modo, ¿cuáles son los beneficios de memsetOver bzeroque la convierten en la opción preferida de facto para los programas más nuevos?

PseudoPsique
fuente
28
"¿Por qué usar bzero sobre memset?" - No lo hagas. Memset es estándar, bzero no lo es.
30
bzero es un BSDism (). memset () es ansi-c. hoy en día, bzero () probablemente se implementará como una macro. Pídale a su profesor que se afeite y lea algunos libros. La eficiencia es un argumento falso. Una llamada al sistema o un cambio de contexto puede costar fácilmente decenas de miles de tics de reloj, una pasada sobre un búfer se ejecuta a la velocidad del bus. Si desea optimizar los programas de red: minimice el número de llamadas al sistema (leyendo / escribiendo fragmentos más grandes)
wildplasser
77
La idea que memsetpuede ser un poco menos eficiente debido a "un poco más de comprobación en marcha" es definitivamente un caso de optimización prematura: cualesquiera que sean las ganancias que pueda ver al omitir una o dos instrucciones de la CPU no valen la pena cuando puede poner en peligro la portabilidad de su código. bzeroes obsoleto, y esa es razón suficiente para no usarlo.
dasblinkenlight
44
A menudo, puede agregar un inicializador `= {0}` en su lugar, y no llamar a una función en absoluto. Esto se hizo más fácil cuando alrededor del cambio de siglo C dejó de requerir una declaración inicial de variables locales. Sin embargo, algunos documentos realmente viejos todavía están atascados en el siglo anterior.
MSalters
1
@SSAnne no, pero probablemente se originó en un libro recomendado para el curso en el que estuvo influenciado, como se menciona en una de las respuestas a continuación: stackoverflow.com/a/17097072/1428743
PseudoPsyche

Respuestas:

152

No veo ninguna razón para preferir bzerosobre memset.

memsetes una función estándar de C mientras que bzeronunca ha sido una función estándar de C. La razón es probablemente porque puede lograr exactamente la misma funcionalidad utilizando la memsetfunción.

Ahora con respecto a la eficiencia, los compiladores gccusan implementaciones incorporadas para las memsetcuales cambian a una implementación particular cuando 0se detecta una constante . Lo mismo para glibccuando los builtins están deshabilitados.

ouah
fuente
Gracias. Esto tiene sentido. Estaba bastante seguro de que memsetsiempre debería usarse en este caso, pero estaba confundido sobre por qué no lo estábamos usando. Gracias por aclarar y reafirmar mis pensamientos.
PseudoPsyche
1
He tenido muchos problemas con bzeroimplementaciones rotas . En matrices no alineadas, solía sobrepasar la longitud proporcionada y poner a cero un poco más de bytes. Nunca tuve tal problema después de cambiar a memset.
rustyx
No olvide memset_squé debe usarse si desea asegurarse de que el compilador no optimice silenciosamente una llamada a "borrar" la memoria para algún propósito relacionado con la seguridad (como borrar una región de memoria que contenía un elemento sensible información como una contraseña de texto sin cifrar).
Christopher Schultz el
69

Supongo que usaste (o tu maestro fue influenciado por) la programación de la red UNIX por W. Richard Stevens. Él usa con bzerofrecuencia en lugar de memset, incluso en la edición más actualizada. El libro es tan popular que creo que se ha convertido en una expresión idiomática en la programación de redes, por lo que todavía lo ves usado.

Me quedaría con memsetsimplemente porque bzeroestá en desuso y reduce la portabilidad. Dudo que vea ganancias reales al usar uno sobre el otro.

austin
fuente
44
Estarías en lo correcto. No necesitábamos libros de texto para este curso, pero acabo de revisar el plan de estudios nuevamente y la Programación de red UNIX aparece como un recurso opcional. Gracias.
PseudoPsyche
9
En realidad es peor que eso. Fue desaprobado en POSIX.1-2001 y eliminado en POSIX.1-2008.
paxdiablo
9
Citando la página 8 de la tercera edición de UNIX Network Programming por W. Richard Stevens - De hecho, el autor de TCPv3 cometió el error de intercambiar los argumentos segundo y tercero a memset en 10 casos de la primera impresión. El compilador de CA no puede detectar este error porque ambas ocurrencias son iguales ... fue un error y podría evitarse usando bzero, ya que intercambiar los dos argumentos a bzero siempre será detectado por el compilador de C si se utilizan prototipos de funciones. Sin embargo, como señaló Paxdiablo, bzero está en desuso.
Aaron Newton
@AaronNewton, deberías agregar eso a la respuesta de Michael ya que confirma lo que dijo.
Synetech
52

La única ventaja que creo que bzero()tiene memset()para establecer la memoria en cero es que hay una posibilidad reducida de cometer un error.

Más de una vez me encontré con un error que se parecía a:

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

El compilador no se quejará (aunque quizás aumenten algunos niveles de advertencia en algunos compiladores) y el efecto será que no se borrará la memoria. Debido a que esto no destruye el objeto, solo lo deja solo, hay una posibilidad decente de que el error no se manifieste en algo obvio.

El hecho de que bzero()no sea estándar es un irritante menor. (FWIW, no me sorprendería si la mayoría de las llamadas a funciones en mis programas no son estándar; de hecho, escribir esas funciones es mi trabajo).

En un comentario a otra respuesta aquí, Aaron Newton citó lo siguiente de Unix Network Programming, Volumen 1, 3ra Edición de Stevens, et al., Sección 1.2 (énfasis agregado):

bzerono es una función ANSI C. Se deriva del código de red de Berkely temprano. Sin embargo, lo usamos en todo el texto, en lugar de la memsetfunción ANSI C , porque bzeroes más fácil de recordar (con solo dos argumentos) que memset(con tres argumentos). Casi todos los proveedores que admiten la API de sockets también proporcionan bzero, y si no, proporcionamos una definición de macro en nuestro unp.hencabezado.

De hecho, el autor de TCPv3 [TCP / IP Ilustrado, Volumen 3 - Stevens 1996] cometió el error de intercambiar el segundo y el tercer argumento por memset10 ocurrencias en la primera impresión . El compilador de CA no puede detectar este error porque ambos argumentos son del mismo tipo. (En realidad, el segundo argumento es un inty el tercer argumento es size_t, que generalmente es un unsigned int, pero los valores especificados, 0 y 16, respectivamente, todavía son aceptables para el otro tipo de argumento). La llamada a memsettodavía funcionó, porque solo un Algunas de las funciones de socket realmente requieren que los 8 bytes finales de una estructura de dirección de socket de Internet se establezcan en 0. Sin embargo, fue un error y uno que podría evitarse utilizando bzero, porque intercambiar los dos argumentos bzerosiempre será captado por el compilador de C si se utilizan prototipos de funciones.

También creo que la gran mayoría de las llamadas a memset()son de memoria cero, entonces, ¿por qué no usar una API que se adapte a ese caso de uso?

Un posible inconveniente bzero()es que es probable que los compiladores se optimicen más memcpy()porque es estándar y, por lo tanto, podrían escribirse para reconocerlo. Sin embargo, tenga en cuenta que el código correcto sigue siendo mejor que el código incorrecto que se ha optimizado. En la mayoría de los casos, el uso bzero()no causará un impacto notable en el rendimiento de su programa, y ​​eso bzero()puede ser una función macro o en línea que se expande memcpy().

Michael Burr
fuente
Sí, supongo que esto podría ser un razonamiento cuando se trabaja en un salón de clases como este, para que sea potencialmente menos confuso para los estudiantes. Sin embargo, no creo que este fuera el caso con mi profesor. Era un gran maestro de RTFM. Si tuviera una pregunta que pudiera ser respondida por el manual, él abriría las páginas del manual en el proyector en clase y se lo mostraría. Estaba muy interesado en inculcar en la mente de todos que el manual está ahí para ser leído y responde la mayoría de sus preguntas. Estoy agradecido por esto, a diferencia de otros profesores.
PseudoPsyche
55
Creo que este es un argumento que puede hacerse incluso fuera del aula: he visto este error en el código de producción. Me parece un error fácil de cometer. También supongo que la gran mayoría de las memset()llamadas son simplemente poner a cero un bloque de memoria, lo que creo que es otro argumento bzero(). ¿Qué significa la 'b' en bzero()cualquier caso?
Michael Burr
77
+1. Eso memsetviola un orden común de parámetros de "buffer, buffer_size" lo hace particularmente propenso a errores IMO.
jamesdlin
En Pascal lo evitan llamándolo "fillchar" y se necesita un char. La mayoría de los compiladores de C / C ++ elegirían ese. Lo que me hace preguntarme por qué los compiladores no dicen "estás pasando un puntero de 32/64 bits donde se espera un byte" y te patean firmemente en los errores del compilador.
Más
1
@Gewure segundo y tercer argumento están en orden incorrecto; la llamada a la función citada no hace exactamente nada
Ichthyo
4

Quería mencionar algo sobre el argumento bzero vs. memset. Instale ltrace y luego compare lo que hace debajo del capó. En Linux con libc6 (2.19-0ubuntu6.6), las llamadas realizadas son exactamente las mismas (vía 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)

Me han dicho que a menos que esté trabajando en las profundidades de libc o en cualquier número de interfaz kernel / syscall, no tengo que preocuparme por ellos. Todo lo que debería preocuparme es que la llamada satisfaga el requisito de poner a cero el búfer. Otros han mencionado sobre cuál es preferible sobre el otro, así que me detendré aquí.

chicle
fuente
Esto sucede porque algunas versiones de GCC emitirán código para memset(ptr, 0, n)cuando lo vean bzero(ptr, n)y no pueden convertirlo a código en línea.
zwol
@zwol En realidad es una macro.
SS Anne
1
@SSAnne gcc 9.3 en mi computadora realiza esta transformación por sí mismo, sin ninguna ayuda de las macros en los encabezados del sistema. extern void bzero(void *, size_t); void clear(void *p, size_t n) { bzero(p, n); }produce una llamada a memset. (Incluir stddef.hpara size_tsin cualquier otra cosa que pueda interferir.)
Zwol
4

Probablemente no deberías usar bzero, en realidad no es C estándar, fue una cosa POSIX.

Y tenga en cuenta que la palabra "era": se desaprobó en POSIX.1-2001 y se eliminó en POSIX.1-2008 en deferencia a memset, por lo que es mejor que use la función C estándar.

paxdiablo
fuente
¿Qué quieres decir con estándar C? ¿Quiere decir que no se encuentra en la biblioteca estándar de C?
Koray Tugay
@Koray, el estándar C significa el estándar ISO y, sí, bzerono es parte de eso.
paxdiablo
No, quiero decir, no sé a qué te refieres con ningún estándar. ¿El estándar ISO significa la biblioteca C estándar? Eso viene con el idioma? ¿La biblioteca mínima que sabemos que estará allí?
Koray Tugay
2
@Koray, ISO es la organización de estándares que es responsable del estándar C, el actual es C11 y los anteriores C99 y C89. Establecen las reglas que debe seguir una implementación para ser considerado C. Entonces sí, si el estándar dice que una implementación debe proporcionar memset, estará allí para usted. De lo contrario, no es C.
paxdiablo
2

Para la función memset, el segundo argumento es an inty el tercer argumento es size_t,

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

que normalmente es un unsigned int, pero si los valores como, 0 and 16para el segundo y el tercer argumento, respectivamente, se ingresan en un orden incorrecto como 16 y 0, entonces, tal llamada a memset aún puede funcionar, pero no hará nada. Porque el número de bytes para inicializar se especifica como 0.

void bzero(void *s, size_t n)

Tal error puede evitarse usando bzero, porque intercambiar los dos argumentos a bzero siempre será captado por el compilador de C si se utilizan prototipos de funciones.

havish
fuente
1
Tal error también se puede evitar con memset si simplemente piensa en la llamada como "establecer esta memoria en este valor para este tamaño", o si tiene un IDE que le proporciona el prototipo o incluso si simplemente sabe lo que es haciendo :-)
paxdiablo
De acuerdo, pero esta función se creó en el momento en que dichos IDE inteligentes no estaban disponibles para el soporte.
Havish
2

En resumen: memset requieren más operaciones de montaje entonces bzero.

Esta es la fuente: http://fdiv.net/2009/01/14/memset-vs-bzero-ultimate-showdown

Tal Bar
fuente
Sí, eso es algo que mencioné en el OP. De hecho, incluso me vinculé a esa página exacta. Resulta que no parece hacer mucha diferencia debido a algunas optimizaciones del compilador. Para más detalles ver la respuesta aceptada por ouah.
PseudoPsyche
66
Esto solo muestra que una implementación de basura de memset es lenta. En MacOS X y algunos otros sistemas, memset usa código que se configura en el momento del arranque dependiendo del procesador que esté utilizando, hace uso completo de los registros de vectores, y para tamaños grandes usa instrucciones de captación previa de maneras inteligentes para obtener el último bit de velocidad
gnasher729
menos instrucciones no significa una ejecución más rápida. De hecho, las optimizaciones a menudo aumentan el tamaño binario y la cantidad de instrucciones debido al desenrollado del bucle, la función en línea, la alineación del bucle ... Mire cualquier código optimizado decente y verá que a menudo tiene muchas más instrucciones que implementaciones de mierda
phuclv
2

Hazlo como quieras. :-)

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

Tenga en cuenta que:

  1. El original bzerono devuelve nada, memsetdevuelve el puntero vacío ( d). Esto se puede solucionar agregando el typecast para anular en la definición.
  2. #ifndef bzerono le impide ocultar la función original, incluso si existe. Prueba la existencia de una macro. Esto puede causar mucha confusión.
  3. Es imposible crear un puntero de función a una macro. Cuando se usan los bzeropunteros de función, esto no funcionará.
Bruce
fuente
1
¿Cuál es el problema con esto, @Leeor? ¿Antipatía general por macros? ¿O no le gusta el hecho de que esta macro puede confundirse con la función (y posiblemente incluso la oculta)?
Palec
1
@Palec, este último. Ocultar una redefinición como macro puede generar tanta confusión. Otro programador que usa este código cree que está usando una cosa y, sin saberlo, se ve obligado a usar la otra. Esa es una bomba de tiempo.
Leeor
1
Después de pensarlo otra vez, estoy de acuerdo en que esta es una mala solución. Entre otras cosas, encontré una razón técnica: cuando se usa a bzerotravés de punteros de función, esto no funcionará.
Palec
Realmente deberías haber llamado a tu macro algo diferente a bzero. Esto es una atrocidad.
Dan Bechard el
-2

memset toma 3 parámetros, bzero toma 2 en la memoria restringida, ese parámetro adicional tomaría 4 bytes más y la mayoría de las veces se usará para configurar todo a 0

Cielo de la noche
fuente