A principios de 2018, apareció una serie de artículos en nuestro blog dedicados a la sexta verificación del código fuente del proyecto Chromium. El ciclo incluye 8 artículos dedicados a errores y recomendaciones para su prevención. Dos artículos han provocado una acalorada discusión, y hasta ahora rara vez he recibido comentarios sobre los temas tratados en ellos. Tal vez, se deben dar algunas explicaciones adicionales y, como dicen, salpicar el i.
Ha pasado un año desde la redacción de una serie de artículos dedicados a la próxima verificación de los códigos fuente del proyecto Chromium:
- Chromium: sexto control del proyecto y 250 errores
- Hermoso cromo y torpe memset
- ruptura y caída
- Cromo: pérdidas de memoria
- Errores tipográficos de cromo
- Cromo: uso de datos inexactos
- ¿Por qué es importante verificar qué devolvió la función malloc?
- Cromo: otros errores
Los artículos sobre
memset y
malloc han causado, y continúan causando, discusiones que me parecen extrañas. Aparentemente, hubo algunos malentendidos debido al hecho de que no formulé claramente mis pensamientos. Decidí volver a estos artículos y hacer algunas aclaraciones.
memset
Comencemos con el artículo sobre
memset , porque todo es simple aquí. Hubo controversia sobre la mejor manera de inicializar las estructuras. Muchos programadores escribieron que es mejor dar una recomendación de no escribir:
HDHITTESTINFO hhti = {};
Y entonces:
HDHITTESTINFO hhti = { 0 };
Argumentos:
- La construcción {0} es más fácil de notar al leer el código que {}.
- La construcción {0} es más intuitiva que {}. Es decir, 0 sugiere que la estructura está llena de ceros.
En consecuencia, me ofrecen cambiar este ejemplo de inicialización en el artículo. No estoy de acuerdo con los argumentos y no planeo enmendar el artículo. Ahora justifico mi posición.
Sobre visibilidad. Creo que esto es cuestión de gustos y hábitos. No creo que la presencia de 0 dentro de llaves se modifique fundamentalmente la situación.
Pero con el segundo argumento no estoy de acuerdo en absoluto. Un registro como {0} da una razón para entender mal el código. Por ejemplo, puede calcular que si reemplaza 0 con 1, todos los campos se inicializarán en unidades. Por lo tanto, este estilo de escritura es más dañino que útil.
El analizador PVS-Studio incluso tiene un diagnóstico relacionado
V1009 sobre este tema, cuya descripción daré ahora.
V1009. Verifique la inicialización de la matriz. Solo el primer elemento se inicializa explícitamente.El analizador detectó un posible error debido al hecho de que al declarar una matriz, el valor se especifica para un solo elemento. Por lo tanto, el resto de los elementos se inicializarán implícitamente a cero o al constructor predeterminado.
Considere un ejemplo de código sospechoso:
int arr[3] = {1};
Quizás el programador esperaba que el
arr constara de una unidad, pero esto no es así. La matriz constará de valores 1, 0, 0.
El código correcto es:
int arr[3] = {1, 1, 1};
Tal confusión puede ocurrir debido a la similitud con la construcción
arr = {0} , que inicializa toda la matriz a ceros.
Si se utilizan activamente diseños similares en su proyecto, puede deshabilitar este diagnóstico.
Tampoco se recomienda descuidar la visibilidad del código.
Por ejemplo, el código para codificar valores de color se escribe de la siguiente manera:
int White[3] = { 0xff, 0xff, 0xff }; int Black[3] = { 0x00 }; int Green[3] = { 0x00, 0xff };
Gracias a la inicialización implícita, todos los colores están configurados correctamente, pero es mejor reescribir el código más claramente:
int White[3] = { 0xff, 0xff, 0xff }; int Black[3] = { 0x00, 0x00, 0x00 }; int Green[3] = { 0x00, 0xff, 0x00 };
malloc
Antes de seguir leyendo, le pido que actualice el contenido del artículo "
¿Por qué es importante verificar qué devolvió la función malloc? ". Este artículo ha generado mucha discusión y crítica. Estas son algunas de las discusiones:
reddit.com/r/cpp ,
reddit.com/r/C_Programming ,
habr.com . De vez en cuando me escriben sobre este artículo por correo ahora.
El artículo es criticado por los lectores en los siguientes puntos:
1. Si malloc devolvió NULL , entonces es mejor salir inmediatamente del programa que escribir un montón de ifs e intentar manejar de alguna manera la falta de memoria, por lo que a menudo es imposible ejecutar el programa de todos modos.No llamé para nada hasta el último para lidiar con las consecuencias de quedarse sin memoria, arrojando el error cada vez más alto. Si está permitido que una aplicación se cierre sin previo aviso, entonces que así sea. Para hacer esto, solo se necesita una verificación inmediatamente después de
malloc o usar
xmalloc (consulte el siguiente párrafo).
Me opuse y advertí sobre la ausencia de cheques, por lo cual el programa continúa funcionando "como si nada hubiera pasado". Esto es completamente diferente. Esto es peligroso porque conduce a un comportamiento indefinido, corrupción de datos, etc.
2. No se habló de la solución, que es escribir envoltorios de funciones para asignar memoria, seguido de la verificación o el uso de funciones existentes, como xmalloc .Estoy de acuerdo, me perdí este momento. Al escribir un artículo, simplemente no pensé en cómo solucionar la situación. Para mí era más importante transmitirle al lector cuál es el peligro de una falta de verificación. Cómo solucionar un error ya es una cuestión de gusto y detalles de implementación.
La función
xmalloc no forma parte de la biblioteca estándar de C (consulte "
¿Cuál es la diferencia entre xmalloc y malloc? "). Sin embargo, esta función se puede declarar en otras bibliotecas, por ejemplo, en la biblioteca de
utilidades de GNU (
libiberty de GNU ).
La esencia de la función es que el programa termina si falla la asignación de memoria. Una implementación de esta función podría verse así:
void* xmalloc(size_t s) { void* p = malloc(s); if (!p) { fprintf (stderr, "fatal: out of memory (xmalloc(%zu)).\n", s); exit(EXIT_FAILURE); } return p; }
En consecuencia, al llamar a la función
xmalloc en todas partes en lugar de
malloc, puede estar seguro de que el programa no tendrá un comportamiento indefinido debido al uso del puntero nulo.
Desafortunadamente,
xmalloc tampoco es una panacea para todos los males. Debe recordarse que el uso de
xmalloc es inaceptable cuando se trata de escribir código de biblioteca. Hablaré de esto un poco más tarde.
3. La mayoría de los comentarios fueron los siguientes: "en la práctica, malloc nunca devuelve NULL ".Afortunadamente, no entiendo solo que este es el enfoque equivocado. Realmente me gustó este
comentario en mi soporte:
Por la experiencia de discutir un tema así, uno tiene la sensación de que hay dos sectas en Internet. Los partidarios de los primeros están firmemente convencidos de que bajo Linux, malloc nunca devuelve NULL. Los partidarios del segundo están firmemente convencidos de que si la memoria en el programa no se puede asignar, pero en principio no se puede hacer nada, solo tiene que caer. No puedes convencerlos de ninguna manera. Especialmente cuando estas dos sectas se cruzan. Solo puedes darlo por sentado. Y no importa en qué recurso de perfil esté la discusión.Pensé y decidí seguir los consejos y no intentaré persuadir :). Esperemos que estos equipos de desarrollo escriban solo programas no críticos. Si, por ejemplo, algunos datos se deterioran en el juego o el juego falla, no es gran cosa.
Lo único importante es que los desarrolladores de bibliotecas, bases de datos, etc. no lo hagan.
Una llamada a la biblioteca y desarrolladores de código responsables
Si está desarrollando una biblioteca u otro código responsable, siempre verifique el valor del puntero devuelto por la función
malloc / realloc , y devuelva el código de error afuera si no se pudo asignar memoria.
En las bibliotecas, no puede llamar a la función de
salida si no pudo asignar memoria. Por la misma razón, no puedes usar
xmalloc . Para muchas aplicaciones, es inaceptable simplemente tomarlas y bloquearlas. Debido a esto, por ejemplo, la base de datos puede estar dañada. Los datos que se han contado durante muchas horas pueden perderse. Debido a esto, el programa puede ser vulnerable a las vulnerabilidades de denegación de servicio, cuando en lugar de manejar correctamente la carga creciente, la aplicación multiproceso simplemente finaliza.
No se puede suponer cómo y en qué proyectos se utilizará la biblioteca. Por lo tanto, se debe suponer que la aplicación puede resolver tareas muy importantes. Y simplemente "matarlo" llamando a la
salida no es bueno. Lo más probable es que dicho programa esté escrito teniendo en cuenta la posibilidad de quedarse sin memoria y puede hacer algo en este caso. Por ejemplo, algunos sistemas CAD, debido a la fuerte fragmentación de la memoria, no pueden asignar un búfer de memoria suficiente para la próxima operación. Pero esta no es una razón para que ella termine en modo de emergencia con pérdida de datos. Un programa puede permitir guardar un proyecto y reiniciarse en modo normal.
En ningún caso puede confiar en el hecho de que
malloc siempre puede asignar memoria. No se sabe en qué plataforma y cómo se utilizará la biblioteca. Si en una plataforma la falta de memoria es exótica, en otra puede ser una situación muy común.
No se puede esperar que si
malloc devuelve
NULL , el programa se bloqueará. Cualquier cosa puede pasar. Como describí en el
artículo , un programa puede escribir datos en una dirección que no sea cero. Como resultado, algunos datos pueden estar dañados, lo que lleva a consecuencias impredecibles. Incluso
memset es peligroso. Si los datos se completan en el orden inverso, al principio algunos datos se corrompen, y solo entonces el programa se bloqueará. Pero la caída puede suceder demasiado tarde. Si los datos corruptos se usan en subprocesos paralelos en el momento de la función
memset , las consecuencias pueden ser fatales. Puede obtener una transacción dañada en la base de datos o enviar un comando para eliminar archivos "innecesarios". Cualquier cosa puede suceder a tiempo. Sugiero al lector que imagine independientemente a qué puede conducir el uso de basura en la memoria.
Por lo tanto, la biblioteca solo tiene una opción correcta para trabajar con funciones
malloc . Debe comprobar INMEDIATAMENTE que la función ha regresado y, si es NULL, devolver el estado del error.
Enlaces de sitio
- Manejo de OOM .
- Diversión con punteros NULL: parte 1 , parte 2 .
- Lo que todo programador de C debe saber sobre el comportamiento indefinido: parte 1 , parte 2 , parte 3 .

Si desea compartir este artículo con una audiencia de habla inglesa, utilice el enlace a la traducción: Andrey Karpov.
Sexto control de cromo, epílogo .