Cuando se pone a cero una estructura como sockaddr_in, sockaddr_in6 y addrinfo antes de usar, lo que es correcto: memset, un inicializador o cualquiera de los dos?

Cada vez que veo código real o código de socket de ejemplo en libros, páginas de manual 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); 

También se puede encontrar lo mismo para establecer las sugerencias struct addrinfo en cero antes de pasarlas a getaddrinfo, por ejemplo.

¿Por qué es esto? Según tengo entendido, es probable que los ejemplos que no usan memset sean equivalentes a los que lo hacen, si no mejores. Me doy cuenta de que hay diferencias:

  • memset pondrá todos los bits a cero, lo que no es necesariamente la representación de bits correcta para establecer cada miembro en 0.
  • memset también pondrá bits de relleno a cero.

¿Alguna de estas diferencias es relevante o requiere un comportamiento cuando se establecen 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 tiende 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 el mejor estilo.

La práctica habitual es usar un inicializador con preferencia a memset precisamente porque no se desea la totalidad de los bits cero y, en cambio, queremos la representación correcta de cero para el tipo (s). ¿Es todo lo contrario para estas estructuras relacionadas con socket?

En mi investigación encontré 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 hace mención de cómo debe estar a cero (memset o inicializador?). Me doy cuenta de que los sockets BSD son anteriores a POSIX y no es el único estándar, ¿por lo tanto, son sus consideraciones de compatibilidad para sistemas heredados o sistemas modernos que no son POSIX?

Personalmente, prefiero desde el punto de vista del estilo (y quizás la buena práctica) usar un inicializador y evitar el memset por completo, pero me resisto porque:

  • Otro código fuente y textos semicanónicos como la Progtwigción de red UNIX usan bzero (por ejemplo, página 101 en la 2ª edición y página 124 en la 3ª edición (soy propietario de ambos)).
  • Soy muy consciente de que no son idénticos, por las razones expuestas anteriormente.

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 lo suficientemente alto; generalmente uso ‘ -Wall ‘ y con frecuencia ‘ -Wextra ‘) . Con el enfoque de inicializador designado, no se debe dar esa advertencia, pero C99 aún no se usa ampliamente, aunque estas partes están bastante disponibles, excepto, quizás, en el mundo de Microsoft.

Suelo acostumbrarme a 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 comstackdor no witter (no debería witter). La asignación utiliza la copia de memoria innata del comstackdor que no será más lenta que una llamada de función a menos que el comstackdor tenga una deficiencia grave.


GCC ha cambiado con el tiempo

Las versiones 4.4.2 a 4.6.0 de 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 comstack 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 comstack 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 comstack 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 comstack 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 comstackdores de arriba fueron comstackdos por mí. Los comstackdores provistos 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ñaló SecurityMatt en un comentario a continuación, la ventaja de memset() sobre la copia de una estructura desde la memoria es que la copia desde la memoria es más costosa, ya que requiere acceso a dos ubicaciones de memoria (origen y destino) en lugar de solo una. En comparación, establecer los valores en cero no tiene que acceder a la memoria para la fuente, y en los sistemas modernos, la memoria es un cuello de botella. Por lo tanto, la encoding de memset() debería ser más rápida que la copia para los inicializadores simples (donde el mismo valor, normalmente todos los bytes de cero, se coloca en la memoria de destino). Si los inicializadores son una mezcla compleja de valores (no todos cero bytes), entonces el balance puede cambiarse a favor de un inicializador, para una compacidad y confiabilidad notacionales, si no otra cosa.

No hay una sola respuesta cortada y seca … probablemente nunca la hubo, y no la hay ahora. Todavía tiendo a usar inicializadores, pero memset() es a menudo una alternativa válida.

Yo diría que ninguno de los dos es correcto porque nunca debe crear objetos de tipo sockaddr_ sí mismo. En su lugar, utilice siempre getaddrinfo (o, a veces, getsockname o getpeername ) para obtener direcciones.

“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.

No debería haber un problema con ninguno de los dos enfoques: los valores de los bytes de relleno no deberían importar. Sospecho que el uso de memset () se deriva del uso anterior de Berkeley-ism bzero (), que puede haber sido anterior a la introducción de inicializadores de estructura o haber sido más eficiente.

Cualquiera de las dos es correcta como muchos han señalado. Además, puede asignar estas estructuras con calloc, que ya devuelve un bloque de memoria con cero.