bzero - para que se utiliza memset en c
Al poner a cero una estructura como sockaddr_in, sockaddr_in6 y addrinfo antes del uso, ¿cuál es la correcta: memset, initializer o cualquiera? (5)
Cada vez que veo un código real o un código de socket de ejemplo en libros, páginas man y sitios web, casi siempre veo algo como:
struct sockaddr_in foo;
memset(&foo, 0, sizeof foo);
/* or bzero(), which POSIX marks as LEGACY, and is not in standard C */
foo.sin_port = htons(42);
en lugar de:
struct sockaddr_in foo = { 0 };
/* if at least one member is initialized, all others are set to
zero (as though they had static storage duration) as per
ISO/IEC 9899:1999 6.7.8 Initialization */
foo.sin_port = htons(42);
o:
struct sockaddr_in foo = { .sin_port = htons(42) }; /* New in C99 */
o:
static struct sockaddr_in foo;
/* static storage duration will also behave as if
all members are explicitly assigned 0 */
foo.sin_port = htons(42);
Lo mismo se puede encontrar para establecer sugerencias struct addrinfo en cero antes de pasarlo a getaddrinfo, por ejemplo.
¿Por qué es esto? Por lo que yo entiendo, los ejemplos que no usan memset probablemente sean el equivalente al que sí lo hace, si no mejor. Me doy cuenta de que hay diferencias:
- memset establecerá todos los bits a cero, que no es necesariamente la representación de bits correcta para configurar cada miembro en 0.
- memset también establecerá los bits de relleno a cero.
¿Alguna de estas diferencias es un comportamiento relevante o necesario al establecer estas estructuras en cero y, por lo tanto, usar un inicializador es incorrecto? Si es así, ¿por qué y qué estándar u otra fuente verifica esto?
Si ambos son correctos, ¿por qué memset / bzero tienden a aparecer en lugar de un inicializador? ¿Es solo una cuestión de estilo? Si es así, está bien, no creo que necesitemos una respuesta subjetiva sobre cuál es mejor estilo.
La práctica habitual es utilizar un inicializador en lugar de memset precisamente porque todos los bits cero normalmente no son deseados y en su lugar queremos la representación correcta de cero para el tipo (s). ¿Es verdadero lo opuesto para estas estructuras relacionadas con el socket?
En mi investigación descubrí que POSIX solo parece requerir que sockaddr_in6 (y no sockaddr_in) se ponga a cero en http://www.opengroup.org/onlinepubs/000095399/basedefs/netinet/in.h.html pero no menciona cómo debe ponerse a cero (memset o initializer?). Me doy cuenta de que los zócalos BSD son anteriores a POSIX y no es el único estándar, ¿cuáles son sus consideraciones de compatibilidad para sistemas heredados o sistemas modernos que no son POSIX?
Personalmente, prefiero un punto de vista de estilo (y tal vez una buena práctica) para usar un inicializador y evitar memset por completo, pero soy reacio porque:
- Otros códigos fuente y textos semi-canónicos como la Programación de Red UNIX usan bzero (por ejemplo, la página 101 en la 2ª edición y la página 124 en la 3ª edición (tengo ambas)).
- Soy muy consciente de que no son idénticos, por las razones indicadas anteriormente.
"struct sockaddr_in foo = {0};" solo es válido por primera vez, mientras que "memset (& foo, 0, sizeof foo);" lo borrará cada vez que se ejecute la función.
Cualquiera de los dos es correcto, como muchos han señalado. Además, puede asignar estas estructuras con calloc que ya devuelve un bloque de memoria con cero.
Diría que ninguno de los dos es correcto porque nunca debes crear objetos de tipo sockaddr_
. En cambio, siempre use getaddrinfo
(oa veces getsockname
o getpeername
) para obtener direcciones.
No debería haber ningún problema con ninguno de los enfoques; los valores de los bytes de relleno no deberían importar. Sospecho que el uso de memset () proviene del uso anterior de Berkeley-ism bzero (), que puede haber sido anterior a la introducción de inicializadores de estructuras o ser más eficiente.
Un problema con el enfoque de inicializadores parciales (que es '' { 0 }
'') es que GCC le advertirá que el inicializador está incompleto (si el nivel de advertencia es suficientemente alto, generalmente uso '' -Wall
'' y a menudo '' -Wextra
'') . Con el enfoque de inicializador designado, esa advertencia no debería darse, pero C99 todavía no se usa ampliamente, aunque estas partes están bastante ampliamente disponibles, excepto, tal vez, en el mundo de Microsoft.
Yo tiendo a favorecer un enfoque:
static const struct sockaddr_in zero_sockaddr_in;
Seguido por:
struct sockaddr_in foo = zero_sockaddr_in;
La omisión del inicializador en la constante estática significa que todo es cero, pero el compilador no será (no debería). La asignación utiliza la copia de memoria innata del compilador que no será más lenta que una llamada de función a menos que el compilador sea muy deficiente.
GCC ha cambiado con el tiempo
Las versiones 4.4.2 a 4.6.0 del GCC generan diferentes advertencias de GCC 4.7.1. Específicamente, GCC 4.7.1 reconoce el inicializador = { 0 }
como un ''caso especial'' y no se queja, mientras que GCC 4.6.0 etc. se quejó.
Considere el archivo init.c
:
struct xyz
{
int x;
int y;
int z;
};
struct xyz xyz0; // No explicit initializer; no warning
struct xyz xyz1 = { 0 }; // Shorthand, recognized by 4.7.1 but not 4.6.0
struct xyz xyz2 = { 0, 0 }; // Missing an initializer; always a warning
struct xyz xyz3 = { 0, 0, 0 }; // Fully initialized; no warning
Cuando se compila con GCC 4.4.2 (en Mac OS X), las advertencias son:
$ /usr/gcc/v4.4.2/bin/gcc -O3 -g -std=c99 -Wall -Wextra -c init.c
init.c:9: warning: missing initializer
init.c:9: warning: (near initialization for ‘xyz1.y’)
init.c:10: warning: missing initializer
init.c:10: warning: (near initialization for ‘xyz2.z’)
$
Cuando se compila con GCC 4.5.1, las advertencias son:
$ /usr/gcc/v4.5.1/bin/gcc -O3 -g -std=c99 -Wall -Wextra -c init.c
init.c:9:8: warning: missing initializer
init.c:9:8: warning: (near initialization for ‘xyz1.y’)
init.c:10:8: warning: missing initializer
init.c:10:8: warning: (near initialization for ‘xyz2.z’)
$
Cuando se compila con GCC 4.6.0, las advertencias son:
$ /usr/gcc/v4.6.0/bin/gcc -O3 -g -std=c99 -Wall -Wextra -c init.c
init.c:9:8: warning: missing initializer [-Wmissing-field-initializers]
init.c:9:8: warning: (near initialization for ‘xyz1.y’) [-Wmissing-field-initializers]
init.c:10:8: warning: missing initializer [-Wmissing-field-initializers]
init.c:10:8: warning: (near initialization for ‘xyz2.z’) [-Wmissing-field-initializers]
$
Cuando se compila con GCC 4.7.1, las advertencias son:
$ /usr/gcc/v4.7.1/bin/gcc -O3 -g -std=c99 -Wall -Wextra -c init.c
init.c:10:8: warning: missing initializer [-Wmissing-field-initializers]
init.c:10:8: warning: (near initialization for ‘xyz2.z’) [-Wmissing-field-initializers]
$
Los compiladores anteriores fueron compilados por mí. Los compiladores proporcionados por Apple son nominalmente GCC 4.2.1 y Clang:
$ /usr/bin/clang -O3 -g -std=c99 -Wall -Wextra -c init.c
init.c:9:23: warning: missing field ''y'' initializer [-Wmissing-field-initializers]
struct xyz xyz1 = { 0 };
^
init.c:10:26: warning: missing field ''z'' initializer [-Wmissing-field-initializers]
struct xyz xyz2 = { 0, 0 };
^
2 warnings generated.
$ clang --version
Apple clang version 4.1 (tags/Apple/clang-421.11.65) (based on LLVM 3.1svn)
Target: x86_64-apple-darwin11.4.2
Thread model: posix
$ /usr/bin/gcc -O3 -g -std=c99 -Wall -Wextra -c init.c
init.c:9: warning: missing initializer
init.c:9: warning: (near initialization for ‘xyz1.y’)
init.c:10: warning: missing initializer
init.c:10: warning: (near initialization for ‘xyz2.z’)
$ /usr/bin/gcc --version
i686-apple-darwin11-llvm-gcc-4.2 (GCC) 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.11.00)
Copyright (C) 2007 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$
Como señala SecurityMatt en un comentario a continuación, la ventaja de memset()
al copiar una estructura de la memoria es que la copia de la memoria es más costosa, requiriendo acceso a dos ubicaciones de memoria (origen y destino) en lugar de solo una. En comparación, establecer los valores en ceros no tiene que acceder a la memoria para obtener la fuente, y en los sistemas modernos, la memoria es un cuello de botella. Por lo tanto, la codificación memset()
debe ser más rápida que la copia para los inicializadores simples (donde el mismo valor, normalmente todos los bytes cero, se coloca en la memoria de destino). Si los inicializadores son una mezcla compleja de valores (no todos los bytes cero), entonces la balanza puede cambiarse a favor de un inicializador, para compacidad notoria y confiabilidad si nada más.
No hay una única respuesta abreviada ... probablemente nunca haya existido, y no hay ahora. Todavía tiendo a usar inicializadores, pero memset()
suele ser una alternativa válida.