c++ - prefijos - ¿Cuáles son los problemas de una cadena terminada en cero que las cadenas con prefijo de longitud superan?
cadena de caracteres en c (6)
¿Cuáles son los problemas de una cadena terminada en cero que las cadenas con prefijo de longitud superan?
Estaba leyendo el libro Write Great Code vol. 1 y tenía esa pregunta en mente.
¿Cuáles son los problemas de una cadena terminada en cero que las cadenas con prefijo de longitud superan?
Ninguno en absoluto.
Es solo un caramelo.
Las cadenas con prefijo de longitud tienen, como parte de su estructura, información sobre cuánto tiempo es la cadena. Si desea hacer lo mismo con cadenas terminadas en cero, puede usar una variable auxiliar;
lpstring = "foobar"; // saves ''6'' somewhere "inside" lpstring
ztstring = "foobar";
ztlength = 6; // saves ''6'' in a helper variable
Muchas funciones de la biblioteca C funcionan con cadenas terminadas en cero y no pueden usar nada más allá del byte ''/0''
. Ese es un problema con las funciones en sí mismas, no con la estructura de cadenas. Si necesita funciones que se ocupen de cadenas terminadas en cero con ceros incrustados, escriba la suya propia.
Algunas características de bonificación adicionales que se pueden implementar con cadenas con prefijo de longitud:
Es posible tener múltiples estilos de prefijo de longitud, identificables a través de uno o más bits del primer byte identificados por el puntero / referencia de cadena. A cambio de un poco más de tiempo para determinar la longitud de la cuerda, uno podría, por ejemplo, usar un prefijo de un solo byte para cadenas cortas y prefijos más largos para cadenas más largas. Si uno usa muchas cadenas de 1-3 bytes que podrían ahorrar más del 50% en el consumo total de memoria para tales cadenas en comparación con el uso de un prefijo fijo de cuatro bytes; dicho formato también podría acomodar cadenas cuya longitud excediera el rango de enteros de 32 bits.
Se pueden almacenar cadenas de longitud variable dentro de límites comprobados a un costo de solo uno o dos bits en el prefijo de longitud. El número N combinado con los otros bits indicaría una de tres cosas:
Una cadena de N-byte
(Opcional) Un búfer de N bytes que contiene una cadena de longitud cero
Un buffer N-byte que, si su último byte B es menor que 248, contiene una cadena de longitud NB-1; si el 248 o más, los bytes B-247 anteriores almacenarían la diferencia entre el tamaño del búfer y la longitud de la cadena. Tenga en cuenta que si la longitud de la cadena es precisamente N-1, la cadena irá seguida de un byte NUL y, si es menor, el byte que sigue a la cadena no se utilizará y podría establecerse en NUL.
Usando tal enfoque, uno necesitaría inicializar búferes fuertes antes del uso (para indicar su longitud), pero ya no necesitaría pasar la longitud de un búfer de cadenas a una rutina que iba a almacenar datos allí.
Uno puede usar ciertos valores de prefijo para indicar varias cosas especiales. Por ejemplo, uno puede tener un prefijo que indica que no es seguido por una cadena, sino más bien por un puntero de cadena de datos y dos enteros que dan el tamaño del búfer y la longitud actual. Si los métodos que operan en cadenas llaman a un método para obtener el puntero de datos, el tamaño del búfer y la longitud, uno puede pasar a dicho método una referencia a una porción de una cadena a bajo costo siempre que la cadena sobrevivirá a la llamada al método.
Se puede extender la característica anterior con un bit para indicar que los datos de la cadena se encuentran en una región que fue generada por
malloc
y que pueden cambiar de tamaño si es necesario; además, uno podría tener métodos que a veces devuelven una cadena generada dinámicamente asignada en el montón, y algunas veces devuelve una cadena estática inmutable, y hacer que el destinatario ejecute una "liberación de esta cadena si no es estática".
No sé si las implementaciones de cadenas prefijadas implementan todas esas características de bonificación, pero todas pueden acomodarse por un costo muy bajo en espacio de almacenamiento, un costo relativamente bajo en el código y un menor costo en el tiempo de lo que se requeriría para usar NUL- cuerdas terminadas cuya longitud no fue conocida ni corta.
Primero una aclaración: las cadenas de C ++ (es decir, std::string
) no son necesarias para terminar con cero hasta C ++ 11 . Sin embargo, siempre proporcionaron acceso a una cadena C de terminación cero.
Las cadenas estilo C terminan con un carácter 0 por razones históricas .
Los problemas a los que se refiere están principalmente relacionados con problemas de seguridad: las cadenas de extremo cero deben tener un terminador cero. Si no lo tienen (por el motivo que sea), la longitud de la cadena no es confiable y pueden provocar problemas de desbordamiento del búfer (que un atacante malintencionado puede explotar escribiendo datos arbitrarios en lugares donde no debería estar). DEP ayuda a mitigar estos problemas pero está fuera de tema aquí).
Se resume mejor en El error más caro de un byte de Poul-Henning Kamp.
- Costos de rendimiento: es más barato manipular la memoria en fragmentos, lo que no se puede hacer si siempre se debe buscar el carácter NULL. En otras palabras, si sabes de antemano que tienes una cadena de 129 caracteres, probablemente sería más eficiente manipularla en secciones de 64, 64 y 1 bytes, en lugar de carácter por carácter.
Seguridad: Marco A. ya golpeó esto bastante duro. Los búfers de cadenas que se ejecutan de forma excesiva o insuficiente aún son una ruta importante para los ataques de los hackers.
Costos de desarrollo del compilador: los grandes costos están asociados con la optimización de compiladores para cadenas de terminación nula que hubieran sido más fáciles con el formato de dirección y longitud.
Costos de desarrollo de hardware: los costos de desarrollo de hardware también son grandes para las instrucciones específicas de cadena asociadas con las cadenas de terminación nula.
Un problema es que con cadenas terminadas en cero, debe seguir buscando el final de la cadena repetidamente. El ejemplo clásico donde esto es ineficiente se concatena en un buffer:
char buf[1024] = "first";
strcat(buf, "second");
strcat(buf, "third");
strcat(buf, "fourth");
En cada llamada a strcat
el programa debe comenzar desde el principio de la cadena y encontrar el terminador para saber dónde comenzar a anexar. Esto significa que la función pasa cada vez más tiempo buscando el lugar donde anexar a medida que la cadena crece.
Con una cadena con prefijo de longitud, el equivalente de la función strcat
sabría dónde está el final de inmediato, y simplemente actualizaría la longitud después de agregarla.
Existen ventajas y desventajas para cada forma de representar cadenas y si le causan problemas a usted depende de lo que está haciendo con cadenas y qué operaciones deben ser eficientes. El problema descrito anteriormente puede superarse mediante el seguimiento manual del final de la cadena a medida que crece, por lo que al cambiar el código puede evitar el costo de rendimiento.
Un problema es que no puede almacenar caracteres nulos (valor cero) en una cadena terminada en cero. Esto hace que sea imposible almacenar algunas codificaciones de caracteres, así como datos encriptados.
Las cadenas con prefijo de longitud no sufren esa limitación.