El asombroso poder de C corta en ambos sentidos. Esto es lo que debe tener en cuenta y cómo mantener sus programas C en lĆnea recta y estrecha.
por Serdar Yegulalp
Pocos lenguajes de programación pueden igualar a C por pura velocidad y potencia a nivel de mĆ”quina. Esta afirmación era cierta hace 50 aƱos y sigue siendo verdad hoy. Sin embargo, hay una razón por la que los programadores acuƱaron el tĆ©rmino “pistolero” para describir el tipo de poder de C. Si no tiene cuidado, C puede volarle los dedos de los pies o los de otra persona.

Aquà hay cuatro de los errores mÔs comunes que puede cometer con C y cinco pasos que puede seguir para prevenirlos.
Error común de C: no liberar la memoria malloc -ed (o liberarla mÔs de una vez)
Este es uno de los grandes errores en C, muchos de los cuales involucran la administración de la memoria. La memoria asignada (realizada con la malloc función) no se elimina automÔticamente en C. Es el trabajo del programador deshacerse de esa memoria cuando ya no se usa. Si no libera solicitudes de memoria repetidas, terminarÔ con una pérdida de memoria. Intente utilizar una región de la memoria que ya se haya liberado y su programa se bloquearÔ o, lo que es peor, cojearÔ y se volverÔ vulnerable a un ataque mediante ese mecanismo.
Tenga en cuenta que una pƩrdida de memoria solo debe describir situaciones en las que se supone que la memoria debe liberarse, pero no lo estƔ. Si un programa sigue asignando memoria porque la memoria es realmente necesaria y se usa para el trabajo, entonces su uso de la memoria puede ser ineficiente , pero estrictamente hablando no es una fuga.
Error comĆŗn de C: leer una matriz fuera de los lĆmites
AquĆ tenemos otro de los errores mĆ”s comunes y peligrosos en C. Una lectura mĆ”s allĆ” del final de una matriz puede devolver datos basura. Una escritura mĆ”s allĆ” de los lĆmites de una matriz podrĆa corromper el estado del programa, bloquearlo por completo o, lo peor de todo, convertirse en un vector de ataque de malware.
Entonces, Āæpor quĆ© la carga de verificar los lĆmites de una matriz se deja al programador? En la especificación oficial de C, leer o escribir una matriz mĆ”s allĆ” de sus lĆmites es un “comportamiento indefinido”, lo que significa que la especificación no tiene voz en lo que se supone que debe suceder. El compilador ni siquiera estĆ” obligado a quejarse de ello.
C ha favorecido durante mucho tiempo dar energĆa al programador incluso bajo su propio riesgo. Una lectura o escritura fuera de lĆmites no suele ser atrapada por el compilador, a menos que habilite especĆficamente las opciones del compilador para protegerse contra ella. Es mĆ”s, es posible que se exceda el lĆmite de una matriz en tiempo de ejecución de una manera que ni siquiera una verificación del compilador puede evitar.
Error C comĆŗn: no comprobar los resultados de malloc
malloc y calloc (para la memoria previamente puesta a cero) son las funciones de la biblioteca C que obtienen la memoria asignada al montón del sistema. Si no pueden asignar memoria, generan un error. En los dĆas en que las computadoras tenĆan relativamente poca memoria, existĆa una gran posibilidad de que una llamada malloc no tuviera Ć©xito.
A pesar de que las computadoras de hoy tienen gigabytes de RAM para distribuir malloc, siempre existe la posibilidad de que falle, especialmente bajo alta presión de memoria o cuando se asignan grandes bloques de memoria a la vez. Esto es especialmente cierto para los programas C que “asignan en bloque”, un gran bloque de memoria del sistema operativo primero y luego lo dividen para su propio uso. Si esa primera asignación falla porque es demasiado grande, es posible que pueda atrapar ese rechazo, reducir la asignación y ajustar la heurĆstica de uso de memoria del programa en consecuencia. Pero si la asignación de memoria falla sin trabas, todo el programa podrĆa fallar.
Error común de C: uso void* de punteros genéricos a la memoria
Usar void* para seƱalar la memoria es un hƔbito antiguo y malo. Los punteros a la memoria debe ser siempre char*, unsigned char* o uintpr t*. Los conjuntos de compiladores de C modernos deben proporcionar uintpr_t como parte de stdint.h.
Cuando se etiqueta de una de estas formas, estÔ claro que el puntero se refiere a una ubicación de memoria en abstracto en lugar de a algún tipo de objeto indefinido. Esto es doblemente importante si estÔ realizando cÔlculos matemÔticos con punteros. Con uintpr_t* y similares, el elemento de tamaño al que se apunta y cómo se utilizarÔ no son ambiguos. Con void*, no mucho.
5 consejos para evitar errores comunes en C
¿Cómo se evitan estos errores tan comunes cuando se trabaja con memoria, matrices y punteros en C? Tenga en cuenta estos cinco consejos:
Estructura el programa en C para que la propiedad de la memoria se mantenga clara
Si recién estÔ iniciando una aplicación C, vale la pena pensar en la forma en que se asigna y libera la memoria como uno de los principios organizativos del programa. Si no estÔ claro dónde se libera una asignación de memoria determinada o bajo qué circunstancias, estÔ buscando problemas. Haga un esfuerzo adicional para que la propiedad de la memoria sea lo mÔs clara posible. Te harÔs un favor a ti mismo (y a los futuros desarrolladores).
Esta es la filosofĆa detrĆ”s de lenguajes como Rust. Rust hace que sea imposible escribir un programa que se compile correctamente a menos que exprese claramente cómo se posee y se transfiere la memoria. C no tiene tales restricciones, pero es aconsejable adoptar esa filosofĆa como guĆa siempre que sea posible.
Utilice las opciones del compilador de C que protegen contra problemas de memoria
Muchos de los problemas descritos en la primera mitad de este artĆculo se pueden marcar utilizando opciones estrictas del compilador. Las ediciones recientes de gcc, por ejemplo, proporcionan herramientas como AddressSanitizer (āASANā) como una opción de compilación para verificar errores comunes de administración de memoria.
Tenga cuidado, estas herramientas no captan absolutamente todo. Son barandillas; no agarran el volante si sales de la carretera. AdemÔs, algunas de estas herramientas, como ASAN, imponen costos de compilación y tiempo de ejecución, por lo que deben evitarse en las versiones de lanzamiento.
Utilice Cppcheck o Valgrind para analizar el código C en busca de pérdidas de memoria
Cuando los propios compiladores se quedan cortos, otras herramientas intervienen para llenar el vacĆo, especialmente cuando se trata de analizar el comportamiento del programa en tiempo de ejecución.
Cppcheck ejecuta un anÔlisis estÔtico en el código fuente de C para buscar errores comunes en la administración de la memoria y comportamientos indefinidos (entre otras cosas).
Valgrind proporciona un caché de herramientas para detectar errores de memoria y subprocesos en la ejecución de programas C. Esto es mucho mÔs poderoso que usar el anÔlisis en tiempo de compilación, ya que puede derivar información sobre el comportamiento del programa cuando estÔ realmente activo. La desventaja es que el programa se ejecuta a una fracción de su velocidad normal. Pero esto generalmente estÔ bien para probar.
Estas herramientas no son soluciones mÔgicas y no atraparÔn todo. Pero funcionan como parte de una estrategia defensiva general contra la mala gestión de la memoria en C.
Automatice la gestión de la memoria C con un recolector de basura
Dado que los errores de memoria son una fuente evidente de problemas de C, aquà hay una solución sencilla: no administre la memoria en C manualmente. Utilice un recolector de basura.
SĆ, esto es posible en C. Puede usar algo como el recolector de basura Boehm-Demers-Weiser para agregar administración automĆ”tica de memoria a los programas C. Para algunos programas, el uso del colector Boehm puede incluso acelerar las cosas. Incluso se puede utilizar como mecanismo de detección de fugas.
La principal desventaja del recolector de basura Boehm es que no puede escanear ni liberar memoria que usa el malloc predeterminado. Utiliza su propia función de asignación, y solo funciona en la memoria que le asigna especĆficamente.
No uses C cuando otro idioma sea suficiente
Algunas personas escriben en C porque realmente lo disfrutan y lo encuentran fructĆfero. Sin embargo, en general, es mejor usar C solo cuando sea necesario, y luego con moderación, para las pocas situaciones en las que realmente es la opción ideal.
Si tiene un proyecto en el que el rendimiento de la ejecución se verĆ” limitado principalmente por la E/S o el acceso al disco, es poco probable que escribirlo en C lo haga mĆ”s rĆ”pido de la manera que importa, y probablemente solo lo harĆ” mĆ”s propenso a errores y difĆcil de escribir. mantener. El mismo programa bien podrĆa estar escrito en Go o Python.
Otro enfoque es usar C solo para las partes de la aplicación que realmente requieren un alto rendimiento, y un lenguaje mĆ”s confiable, aunque mĆ”s lento, para otras partes. Nuevamente, Python se puede usar para empaquetar bibliotecas C o código C personalizado, lo que lo convierte en una buena opción para los componentes mĆ”s repetitivos como el manejo de opciones de lĆnea de comandos.