una - ¿Qué es un buen patrón de programación para manejar los valores de retorno de las funciones de escritura de archivos stdio?
problemas con struct en c (13)
Estoy trabajando en un código que genera una gran cantidad de
ignoring return value of ‘size_t fwrite(const void*, size_t, size_t, FILE*)’, declared with attribute warn_unused_result
advertencias cuando se compila con g ++, y me pregunto cuál es el mejor patrón de programación para grabar y manejar el valor de retorno de una gran cantidad de fwrite
secuenciales separados (es decir, no el mismo fwrite
en un bucle)
Digamos que el código se ve así en este momento:
fwrite (&blah, sizeof (blah), 1, fp);
// ... more code ...
fwrite (&foo, sizeof (foo), 1, fp);
// ... more code ...
Actualmente estoy pensando en algo como esto, pero puedo tener dificultades para limpiar el puntero del archivo:
if (fwrite (&blah, sizeof (blah), 1, fp) != 1) return someerrorcode;
// ... more code ...
if (fwrite (&foo, sizeof (foo), 1, fp) != 1) return someerrorcode;
// ... more code ...
Creo que ese enfoque es claramente mejor que anidar, lo que sería demasiado loco demasiado rápido:
if (fwrite (&blah, sizeof (blah), 1, fp) == 1) {
// ... more code ...
if (fwrite (&foo, sizeof (foo), 1, fp) == 1) {;
// ... more code ...
}
}
Sin embargo, seguramente ya existe un patrón establecido de mejores prácticas para este tipo de cosas.
Por supuesto, como principalmente estoy buscando esto para deshacerme de las advertencias del compilador, podría asignar el valor de retorno a una variable ficticia e ignorarla, pero me gustaría intentar hacerlo de la manera correcta primero.
dummy = fwrite (&blah, sizeof (blah), 1, fp);
// ... more code ...
dummy = fwrite (&foo, sizeof (foo), 1, fp);
// ... more code ...
Actualización: eliminé la etiqueta c ++ ya que este código realmente solo se compila con g ++, por lo que se necesitan soluciones basadas en c para mantener el resto de la base de código.
¿Por qué no envuelve el fwrite
en un objeto Writer de algún tipo y lanza una excepción si fwrite () devuelve un código de error? Fácil de codificar, fácil de usar, fácil de administrar. En mi humilde opinión, por supuesto. :)
Algo como esto funcionaría
if (fwrite (&blah, sizeof (blah), 1, fp) != 1) throw SomeException;
Si le preocupa que los punteros se limpien, puede envolver el puntero en algún tipo de puntero inteligente antes de ejecutar su Fwrite.
Si no quiere usar punteros inteligentes, esto funcionará, pero es complicado, así que probaría primero la ruta del puntero inteligente
if (fwrite (&blah, sizeof (blah), 1, fp) != 1) {
//cleanup here
throw SomeException;
}
Bueno ... Podría crear una función de envoltura, que vuelva a probar la escritura si falla, tal vez hasta un número máximo de intentos y devuelve éxito / falla:
int safe_fwrite(FILE *file, const void *data, size_t nbytes, unsigned int retries);
void print_and_exit(const char *message);
Entonces su código principal podría escribirse como
#define RETRIES 5
if(!safe_fwrite(fp, &blah, sizeof blah, RETRIES))
print_and_exit("Blah writing failed, aborting");
if(!safe_fwrite(fp, &foo, sizeof foo, RETRIES))
print_and_exit("Foo writing failed, aborting");
El anidamiento es malo y los retornos múltiples tampoco son buenos.
Solía usar el siguiente patrón:
#define SUCCESS (0)
#define FAIL (-1)
int ret = SUCCESS;
if (!fwrite(...))
ret = FAIL;
if (SUCCESS == ret) {
do_something;
do_something_more;
if (!fwrite(...))
ret = FAIL;
}
if (SUCCESS == ret)
do_something;
return ret;
Sé que se ve feo pero tiene un solo punto de retorno, sin anidamiento excesivo y es muy fácil de mantener.
El manejo de excepción de C del pobre se basa en goto (de hecho, la única instancia de goto NO es perjudicial):
int foo() {
FILE * fp = fopen(...);
....
/* Note: fwrite returns the number of elements written, not bytes! */
if (fwrite (&blah, sizeof (blah), 1, fp) != 1) goto error1;
...
if (fwrite (&foo, sizeof (foo), 1, fp) != 1) goto error2;
...
ok:
/* Everything went fine */
fclose(fp);
return 0;
error1:
/* Error case 1 */
fclose(fp);
return -1;
error2:
/* Error case 2 */
fclose(fp);
return -2;
}
Entiendes la idea. Reestructurar como lo desee (devoluciones individuales / múltiples, limpieza única, mensajes de error personalizados, etc.). Desde mi experiencia, este es el patrón de manejo de errores C más común que existe. El punto crucial es: NUNCA, NUNCA ignore los códigos de retorno stdlib, y cualquier buena razón para hacerlo (por ejemplo, legibilidad) no es suficiente.
Haría algo en esta línea:
FILE * file = fopen("foo", "wb");
if(!file) return FAILURE;
// assume failure by default
_Bool success = 0;
do
{
if(!fwrite(&bar, sizeof(bar), 1, file))
break;
// [...]
if(!fwrite(&baz, sizeof(baz), 1, file))
break;
// [...]
success = 1;
} while(0);
fclose(file);
return success ? SUCCESS : FAILURE;
Con un poco de macro magia C99
#define with(SUBJECT, FINALIZE, ...) do { /
if(SUBJECT) do { __VA_ARGS__ } while(0); if(SUBJECT) FINALIZE; /
} while(0)
y el uso de ferror()
lugar de nuestro propio indicador de error como lo sugiere Jonathan Leffler, esto se puede escribir como
FILE * file = fopen("foo", "wb");
with(file, fclose(file),
{
if(!fwrite(&bar, sizeof(bar), 1, file))
break;
// [...]
if(!fwrite(&baz, sizeof(baz), 1, file))
break;
// [...]
});
return file && !ferror(file) ? SUCCESS : FAILURE;
Sin embargo, si hay otras condiciones de error además de los errores de io, aún tendrá que rastrearlas con una o más variables de error.
Además, su cheque contra sizeof(blah)
es incorrecto: fwrite()
devuelve el recuento de los objetos escritos.
Podrías escribir una función de envoltura
void new_fwrite(a, b, c, d) {
if (fwrite (a, b, c, b) != b)
throw exception;
}
y luego reemplazar todas las llamadas a fwrite con new_fwrite
Puede eliminar las advertencias de esta manera:
(void) fwrite ( ,,,, );
Dirigiéndose a su pregunta principal, si cualquiera de las llamadas fwrite () falla, supongo que no tiene sentido continuar, ya que el resultado es presumiblemente corrupto. En ese caso, y cuando etiquetó este C ++, lanzaría una excepción.
Tal vez algo como esto? Capturas los errores sin hacer que el código sea ilegible, y puedes hacer la limpieza después del final del ciclo falso.
#define WRITE_ERROR 100
#define WRITE_OK 0
int do_fwrite(void* ptr, size_t bytes, int fp) {
if ( fwrite(ptr, bytes, 1, fp) != bytes ) return WRITE_ERROR;
return WRITE_OK;
}
int my_func() {
int errcode = 0;
...
do {
if ( errcode = do_fwrite(&blah, sizeof(blah), fp) ) break;
....
if ( errcode = do_fwrite(&foo, sizeof(foo), fp) ) break;
....
etc
} while( false );
fclose(fp);
return errcode;
}
Tu primera solución se ve bien. Por lo general, un goto err;
es más útil ya que es posible que necesite una parte de limpieza común (como rebobinar a una posición conocida).
Para hacer que GCC sea silencioso solo hazlo:
(void)fwrite (&blah, sizeof (blah), 1, fp);
(void)fwrite (&foo, sizeof (foo), 1, fp);
Una solución C potencialmente elegante para esto podría ser algo como esto (advertencia - código no compilado, no compilado más adelante):
size_t written;
int ok = 1;
size_t num_elements = x;
ok = (fwrite(stuff, sizeof(data), num_elements, outfile) == num_elements);
if (ok) {
... do other stuff ...
}
ok = ok && (fwrite(stuff, sizeof(data), num_elements, outfile) == num_elements);
if (ok) {
... etc etc ad nauseam ...
}
fclose(outfile);
return ok;
Lo anterior logra dos objetivos al mismo tiempo:
- Los valores devueltos se verifican, eliminando así la advertencia y dándole la capacidad de devolver un código de estado.
- Gracias a la evaluación de cortocircuito, si una de las llamadas fwrite () falla, las siguientes no se ejecutarán, por lo que al menos la escritura del archivo se detiene en lugar de proporcionarle un archivo potencialmente corrupto si la condición de error desaparece a la mitad de la función y puedes escribir datos otra vez
Desafortunadamente, los bloques "feos" if (ok)
son necesarios si no desea utilizar la evaluación de cortocircuito en todas partes. He visto este patrón usado en funciones comparativamente pequeñas usando la evaluación de cortocircuito en todas partes y creo que es probablemente el más adecuado para ese uso particular.
Ignorar los errores es una mala idea. Es mucho mejor hacer algo desagradable como bloquear el programa para que al menos sepas que algo salió mal, en lugar de proceder silenciosamente. Aún mejor es una buena comprobación y recuperación de errores.
Si está utilizando C ++, puede crear un contenedor RAII para el ARCHIVO * para que siempre se cierre. Mira std :: auto_ptr para obtener ideas. A continuación, puede devolver un código de error útil o de la función cuando lo desee, o lanzar una excepción y no tener que preocuparse por artículos de limpieza olvidados.
Ok, dado que estoy buscando una solución c
(sin excepciones), ¿qué tal:
void safe_fwrite(data,size,count,fp) {
if (fwrite(data,size,count,fp) != count) {
printf("[ERROR] fwrite failed!/n");
fclose(fp);
exit(4);
}
}
Y luego en mi código tengo:
safe_fwrite (&blah, sizeof (blah), 1, fp);
// ... more code ...
safe_fwrite (&foo, sizeof (foo), 1, fp);
// ... more code ...