rescate - Prácticas de manejo de cuerdas en C
nudos con cuerda pdf (7)
Estoy empezando un nuevo proyecto en plano C (c99) que va a funcionar principalmente con texto. Debido a las restricciones del proyecto externo, este código tiene que ser extremadamente simple y compacto, y consiste en un único archivo de código fuente sin dependencias o bibliotecas externas, excepto libc y bibliotecas de sistemas omnipresentes similares.
Con esa comprensión, ¿cuáles son algunas de las mejores prácticas, errores, trucos u otras técnicas que pueden ayudar a que el manejo de las cadenas del proyecto sea más robusto y seguro?
Algunos errores importantes son:
- En C, no hay ninguna relación entre la longitud de la cadena y el tamaño del búfer. Una cadena siempre se ejecuta hasta (e incluye) el primer carácter
''/0''
. Es su responsabilidad como programador asegurarse de que este carácter pueda encontrarse dentro del búfer reservado para esa cadena. - Siempre hacer un seguimiento explícito de los tamaños de búfer. El compilador realiza un seguimiento de los tamaños de matriz, pero esa información se perderá antes de que usted lo sepa.
Algunos pensamientos de un desarrollador integrado desde hace mucho tiempo, la mayoría de los cuales se basan en sus requisitos de simplicidad y no son específicos de C:
Decida qué funciones de manejo de cadenas necesitará y mantenga ese conjunto lo más pequeño posible para minimizar los puntos de falla.
Siga la sugerencia de R. para definir una interfaz clara que sea consistente en todos los manejadores de cadenas. Un conjunto de reglas estricto, pequeño pero detallado, le permite usar la coincidencia de patrones como una herramienta de depuración: puede sospechar de cualquier código que se vea diferente al resto.
Como señaló Bart van Ingen Schenau, rastrea la longitud del búfer independientemente de la longitud de la cuerda. Si siempre trabajará con texto, es seguro utilizar el carácter nulo estándar para indicar el final de la cadena, pero depende de usted asegurarse de que el texto + nulo se ajuste al búfer.
Asegure un comportamiento consistente en todos los manejadores de cadenas, particularmente cuando faltan las funciones estándar: truncamiento, entradas nulas, terminación nula, relleno, etc.
Si absolutamente necesita violar alguna de sus reglas, cree una función separada para ese propósito y asígnele un nombre apropiado. En otras palabras, asigne a cada función un único comportamiento inequívoco. Por lo tanto, puede usar
str_copy_and_pad()
para una función que siempre rellena su objetivo con valores nulos.Siempre que sea posible, use funciones seguras integradas ( por ejemplo,
memmove()
Jonathan Leffler) para hacer el trabajo pesado. ¡Pero pruébalos para asegurarte de que están haciendo lo que crees que están haciendo!Compruebe si hay errores tan pronto como sea posible. Los desbordamientos de búfer no detectados pueden provocar errores de "rebote" que son muy difíciles de localizar.
Escriba pruebas para cada función para asegurarse de que cumple con su contrato. Asegúrese de cubrir los casos de borde (desactivado por 1, nulo / cadenas vacías, superposición de origen / destino, etc. ) Y esto puede sonar obvio, pero asegúrese de entender cómo crear y detectar un agotamiento / saturación del búfer, luego escriba las pruebas Eso explícitamente genera y verifica esos problemas. (Es probable que mis empleados de control de calidad estén hartos de escuchar mis instrucciones de "no solo hacer pruebas para asegurarse de que funcionan, sino pruebas para asegurarse de que no se rompan").
Aquí hay algunas técnicas que me han funcionado:
Cree envoltorios para sus rutinas de administración de memoria que asignan "bytes de valla" en cada extremo de sus búferes durante la asignación y verifíquelos al momento de la desasignación. También puede verificarlos dentro de sus controladores de cadena, tal vez cuando se establece una macro STR_DEBUG. Advertencia : deberá probar sus diagnósticos a fondo, para que no creen puntos de falla adicionales.
Cree una estructura de datos que encapsule tanto el búfer como su longitud. (También puede contener los bytes de la valla si los usa). Advertencia : ahora tiene una estructura de datos no estándar que todo el código base debe administrar, lo que puede significar una reescritura sustancial (y, por lo tanto, puntos de error adicionales).
Haz que tus manejadores de cadenas validen sus entradas. Si una función prohíbe los punteros nulos, verifíquelos explícitamente. Si requiere una cadena válida (como
strlen()
debería) y conoce la longitud del búfer, verifique que el búfer contenga un carácter nulo. En otras palabras, verifique los supuestos que pueda estar haciendo sobre el código o los datos.Escribe tus pruebas primero. Eso le ayudará a comprender el contrato de cada función, exactamente lo que espera de la persona que llama y lo que la persona que llama debe esperar de él. Usted se encontrará pensando en las formas en que lo usará, las formas en que podría romperse y en los casos extremos que debe manejar.
Muchas gracias por hacer esta pregunta! Me gustaría que más desarrolladores pensaran en estos problemas, especialmente antes de que comiencen a codificar. ¡Buena suerte y los mejores deseos para un producto robusto y exitoso!
Cuando se trata de tiempo frente a espacio, no olvides elegir el bit estándar que se ajusta here
Durante mis primeros proyectos de firmware, usé las tablas de consulta para contar el bit establecido en una eficiencia de operación O (1).
Dos centavos:
- Siempre use la versión "n" de las funciones de cadena: strncpy, strncmp, (o wcsncpy, wcsncmp, etc.)
- Asigne siempre usando el idioma +1: por ejemplo, char * str [MAX_STR_SIZE + 1], y luego pase MAX_STR_SIZE como el tamaño para la versión "n" de las funciones de cadena y termine con str [MAX_STR_SIZE] = ''/ 0''; para asegurarse de que todas las cadenas estén debidamente finalizadas.
El paso final es importante ya que la versión "n" de las funciones de cadena no se agregará ''/ 0'' después de copiar si se alcanzó el tamaño máximo.
Eche un vistazo a strlcpy
y strlcat
, consulte el original paper
para obtener más información.
Sin ninguna información adicional sobre lo que está haciendo su código, recomendaría diseñar todas sus interfaces como esta:
size_t foobar(char *dest, size_t buf_size, /* operands here */)
con semántica como snprintf
:
-
dest
apunta a un búfer de tamaño al menosbuf_size
. - Si
buf_size
es cero, los punteros nulos / inválidos son aceptables paradest
y no se escribirá nada. - Si
buf_size
no es cero,dest
siempre termina en nulo. - Cada función
foobar
devuelve la longitud de la salida completa no truncada; la salida se ha truncado sibuf_size
es menor o igual que el valor de retorno.
De esta manera, cuando la persona que llama puede conocer fácilmente el tamaño del búfer de destino que se requiere, se puede obtener un búfer suficientemente grande de antemano. Si la persona que llama no puede saberlo fácilmente, puede llamar a la función una vez con un argumento cero para buf_size
o con un búfer que es "probablemente lo suficientemente grande" y solo puede volver a intentarlo si se queda sin espacio.
También puede hacer una versión asprintf
de tales llamadas análogas a la función asprintf
GNU, pero si desea que su código sea lo más flexible posible, evitaría realizar cualquier asignación en las funciones de cadena reales. El manejo de la posibilidad de falla es siempre más fácil a nivel de la persona que llama, y muchas personas que llaman pueden asegurar que la falla nunca sea una posibilidad utilizando un búfer local o un búfer que se obtuvo mucho antes en el programa para que el éxito o el fracaso de una operación más grande Es atómico (lo que simplifica enormemente el manejo de errores).
Trabaje con matrices en la pila siempre que sea posible e inicialícelos correctamente. No tiene que realizar un seguimiento de las asignaciones, los tamaños y las inicializaciones.
char myCopy[] = { "the interesting string" };
Para cuerdas de tamaño mediano C99 tiene VLA. Son un poco menos utilizables ya que no se pueden inicializar. Pero todavía tienes las dos primeras de las ventajas anteriores.
char myBuffer[n]; myBuffer[0] = ''/0'';