2010-01-04 12 views
47

En la programación de la API Win32 es típico usar C struct s con múltiples campos. Por lo general, solo un par de ellos tienen valores significativos y todos los demás deben ser eliminados. Esto se puede conseguir en cualquiera de las dos maneras:memset() o inicialización de valor para poner a cero una estructura?

STRUCT theStruct; 
memset(&theStruct, 0, sizeof(STRUCT)); 

o

STRUCT theStruct = {}; 

La segunda variante se ve más limpia - es una sola línea, no tiene ningún parámetro que pueda ser mal escritas y conducir a un error que se plantó.

¿Tiene algún inconveniente en comparación con la primera variante? ¿Qué variante usar y por qué?

+0

Esta [respuesta] [1] a una pregunta posterior parece ser más útil y más fácil. [1]: http://stackoverflow.com/questions/4625212/class-initialization-list/4625730#4625730 – TheMatto

Respuesta

73

Esas dos construcciones a muy diferentes en su significado. El primero utiliza una función memset, que está destinada a establecer un búfer de memoria a cierto valor. El segundo a inicializa un objeto. Me explico con un poco de código:

Vamos a suponer que tiene una estructura que tiene miembros única de tipos de POD

struct POD_OnlyStruct 
{ 
    int a; 
    char b; 
}; 

POD_OnlyStruct t = {}; // OK 

POD_OnlyStruct t; 
memset(&t, 0, sizeof t); // OK as well 

En este caso, la escritura de un POD_OnlyStruct t = {}POD_OnlyStruct t; memset(&t, 0, sizeof t) o no hace mucha diferencia , ya que la única diferencia que tenemos aquí es la alineación de bytes que se establece en valor cero en el caso de memset utilizado. Como no tiene acceso a esos bytes normalmente, no hay diferencia para usted.

Por otro lado, ya que lo ha insertado en cuestión como C++, vamos a probar otro ejemplo, con el miembro tipos diferentes de POD:

struct TestStruct 
{ 
    int a; 
    std::string b; 
}; 

TestStruct t = {}; // OK 

{ 
    TestStruct t1; 
    memset(&t1, 0, sizeof t1); // ruins member 'b' of our struct 
} // Application crashes here 

En este caso utilizando una expresión como TestStruct t = {} es bueno y usar un memset en él provocará un bloqueo. Esto es lo que sucede si usa memset - se crea un objeto de tipo TestStruct, creando así un objeto de tipo std::string, ya que es un miembro de nuestra estructura. A continuación, memset establece la memoria donde se encuentra el objeto b a cierto valor, digamos cero. Ahora, una vez que nuestro objeto TestStruct quede fuera del alcance, se destruirá y cuando llegue el turno a su miembro std::string b verá un bloqueo, ya que todas las estructuras internas de ese objeto se arruinaron con el memset.

Por lo tanto, la realidad es, esas cosas son muy diferentes, y aunque a veces es necesario memset toda una estructura de ceros en ciertos casos, siempre es importante asegurarse de que entiende lo que está haciendo, y no cometer un error como en nuestro segundo ejemplo.

Mi voto - memset utilizar en objetos solamente si es necesario, y utilizar la inicialización por defecto x = {} en todos los demás casos.

+0

¡Hola Dimity!Tengo una estructura que tiene algunos miembros y probé la primera opción de memsetting: "struct stVar = {}". Pero recibo la advertencia "-Wmissing-field-initializers". ¿Es un problema? – MayurK

+0

En este caso, por * POD * ¿quiere decir realmente un objeto * trivially construible * (es decir, un objeto sin c-tor proporcionado por el usuario)? No creo que deba limitarse a POD. –

6

Usaría la inicialización de valores porque parece limpia y menos propensa a errores como mencionó. No veo ningún inconveniente en hacerlo.

Puede confiar en memset para poner a cero la estructura después de que se haya utilizado.

5

No es que sea común, pero supongo que la segunda forma también tiene el beneficio de inicializar flotadores a cero. Al hacer un memset ciertamente no

+0

'mientras se realiza un memset definitivamente no' - no del todo cierto. En realidad, en memsetting x86 y x64, un float/double to zero lo pondrá a cero. Claro, esto no está en el estándar C/C++, pero funciona en las plataformas más populares. – sbk

+2

sbk: por ahora ... quién sabe qué implementación de coma flotante podrían comenzar a usar. IEEE 754 no está definido para el compilador. Entonces, aunque funcione ahora, es una suerte para ti, pero puede dar problemas más adelante. – Toad

2

La inicialización del valor porque se puede hacer en tiempo de compilación.
También es correcto 0 inicializa todos los tipos de POD.

El memset() se realiza en tiempo de ejecución.
También el uso de memset() es sospechoso si la estructura no es POD.
No inicializa correctamente (a cero) tipos no enteros.

+3

Los valores no se inicializan en el momento de la compilación. El compilador genera un código de inicio que inicializa todos los globales durante el inicio del programa, así en el tiempo de ejecución. Para las variables de pila, la inicialización se realiza en la entrada de función, nuevamente en tiempo de ejecución. – qrdl

+0

@qrdl, depende del compilador y del destino. Para el código ROM-able, los valores a veces se establecen en tiempo de compilación. –

+2

@qrdl: Permítanme volver a expresar eso. La inicialización del valor puede permitir (en ciertas situaciones) que el compilador realice la inicialización en tiempo de compilación (en lugar de en tiempo de ejecución). Entonces POD only globals se puede inicializar en tiempo de compilación. –

28

Dependiendo de los miembros de la estructura, las dos variantes no son necesariamente equivalentes. memset establecerá la estructura a todos los bits cero mientras que la inicialización de valores inicializará todos los miembros al valor cero. El estándar C garantiza que sean los mismos solo para los tipos integrales, no para los valores de punto flotante o los punteros.

Además, algunas API requieren que la estructura realmente se configure en todos los bits cero. Por ejemplo, la API de socket de Berkeley usa estructuras de forma polimórfica, y allí es importante realmente establecer toda la estructura a cero, no solo los valores que son evidentes. La documentación API debe decir si la estructura realmente necesita ser todo-bits-cero, pero podría ser deficiente.

Pero si ninguno de estos, o un caso similar, se aplica, entonces depende de usted. Al definir la estructura, preferiría la inicialización del valor, ya que eso comunica la intención más claramente. Por supuesto, si necesita poner a cero una estructura existente, memset es la única opción (además de inicializar cada miembro a cero manualmente, pero eso normalmente no se haría, especialmente para estructuras grandes).

+0

por curiosidad, ¿en qué plataforma un flotador con todos los bits a cero no es el cero positivo? –

+3

Varias CPUs anteriores a la IEEE-754 tenían extraños ceros flotantes. Las matemáticas que no sean de 754 podrían regresar aún, nunca se sabe, así que es mejor no escribir esos errores. –

+1

No importa. El estándar C no especifica qué formato de flotación se usa. Entonces, incluso si funciona ahora para IEEE 754, podría no funcionar en una implementación de flotante diferente (futura o pasada) – Toad

-1

Si hay muchos miembros de puntero y es probable que agregue más en el futuro, puede ser útil usar memset. Combinado con las llamadas assert(struct->member) apropiadas, puede evitar bloqueos aleatorios al intentar deferenciar un puntero malo que olvidó inicializar. Pero si no eres tan olvidadizo como yo, entonces la inicialización de miembros es probablemente la mejor.

Sin embargo,, si su estructura se usa como parte de una API pública, debe obtener el código de cliente para usar memset como requisito.Esto ayuda con futuras pruebas, ya que puede agregar nuevos miembros y el código del cliente NULL automáticamente en la llamada memset, en lugar de dejarlos en un estado no inicializado (posiblemente peligroso). Esto es lo que haces cuando trabajas con estructuras de socket, por ejemplo.

+0

¿Cómo ayuda en el futuro a prueba? Si está asumiendo que el código del cliente no se recompila, terminaría llamando a 'memset' con un tamaño de estructura incorrecto. Si se recompila el código del cliente, necesitaría acceder al archivo de encabezado actualizado con la definición de estructura para que funcionen 'memset' o value para que funcionen. (El cliente y la biblioteca necesitan tener una noción consistente de cómo se representa el puntero nulo, sin embargo, si la API recomienda 'memset', debería estar contrastando con todos los bits cero, no contra NULL). – jamesdlin

+0

Además, si la estructura es parte de una API pública, entonces tal vez uno debería considerar una estructura opaca con una función de inicialización. – jamesdlin

3

En algunos compiladores STRUCT theStruct = {}; se traduciría a memset(&theStruct, 0, sizeof(STRUCT)); en el ejecutable. Algunas funciones de C ya están vinculadas para configurar el tiempo de ejecución, por lo que el compilador tiene disponibles estas funciones de biblioteca, como memset/memcpy.

+1

Esto realmente me mordió duro recientemente. Estaba trabajando en una pieza personalizada de código de compresión y estaba inicializando algunas estructuras grandes en tiempo de declaración usando 'struct something foo = {x, y, z}' y cachegrind demostró que el 70% del "trabajo" de mi programa estaba en 'memset' porque las estructuras se pusieron a cero en TODAS las llamadas a funciones. –

10

Si su estructura contiene cosas como:

int a; 
char b; 
int c; 

A continuación se insertará bytes de relleno entre "b" y "c". memset() los pondrá a cero, a la inversa no, por lo que habrá 3 bytes de basura (si sus datos son 32 bits). Si tiene la intención de usar su estructura para leer/escribir desde un archivo, esto podría ser importante.

Cuestiones relacionadas