¿Qué ventaja (s) de los literales de cadena de solo lectura justifican (-ies / -ied) el:
Otra forma de dispararte en el pie
char *foo = "bar"; foo[0] = 'd'; /* SEGFAULT */
Incapacidad para inicializar elegantemente un conjunto de palabras de lectura-escritura en una línea:
char *foo[] = { "bar", "baz", "running out of traditional placeholder names" }; foo[1][2] = 'n'; /* SEGFAULT */
Complicando el lenguaje en sí.
char *foo = "bar"; char var[] = "baz"; some_func(foo); /* VERY DANGEROUS! */ some_func(var); /* LESS DANGEROUS! */
¿Ahorrando memoria? Leí en alguna parte (no pude encontrar la fuente ahora) hace mucho tiempo, cuando la RAM era escasa, los compiladores intentaron optimizar el uso de la memoria fusionando cadenas similares.
Por ejemplo, "more" y "regex" se convertirían en "moregex". ¿Sigue siendo cierto hoy, en la era de las películas digitales con calidad de blu-ray? Entiendo que los sistemas integrados todavía funcionan en un entorno de recursos restringidos, pero aún así, la cantidad de memoria disponible ha aumentado dramáticamente.
Problemas de compatibilidad? Supongo que un programa heredado que intente acceder a la memoria de solo lectura se bloqueará o continuará con un error no descubierto. Por lo tanto, ningún programa heredado debería intentar acceder al literal de cadena y, por lo tanto, permitir escribir en el literal de cadena no dañaría los programas heredados válidos, no hackeados y portátiles .
¿Hay alguna otra razón? ¿Es incorrecto mi razonamiento? ¿Sería razonable considerar un cambio en los literales de cadena de lectura-escritura en los nuevos estándares C o al menos agregar una opción al compilador? ¿Se consideró esto antes o mis "problemas" son demasiado pequeños e insignificantes para molestar a alguien?
Respuestas:
Históricamente (tal vez reescribiendo partes de él), fue todo lo contrario. En las primeras computadoras de principios de la década de 1970 (quizás PDP-11 ) que ejecutaban un C embrionario prototípico (quizás BCPL ) no había MMU ni protección de memoria (que existía en la mayoría de los mainframes IBM / 360 más antiguos ). Por lo tanto, cada byte de memoria (incluidos los que manejan cadenas literales o código de máquina) podría ser sobrescrito por un programa erróneo (imagine un programa cambiando algunos
%
a/
una cadena de formato printf (3) ). Por lo tanto, las cadenas literales y las constantes se podían escribir.Cuando era adolescente en 1975, codifiqué en el museo Palais de la Découverte en París en computadoras antiguas de la década de 1960 sin protección de memoria: IBM / 1620 tenía solo una memoria central, que se podía inicializar a través del teclado, por lo que tenía que escribir varias docenas de dígitos para leer el programa inicial en cintas perforadas; CAB / 500 tenía una memoria de tambor magnético; puede deshabilitar la escritura de algunas pistas a través de interruptores mecánicos cerca del tambor.
Más tarde, las computadoras obtuvieron algún tipo de unidad de administración de memoria (MMU) con cierta protección de memoria. Había un dispositivo que prohibía a la CPU sobrescribir algún tipo de memoria. Entonces, algunos segmentos de memoria, en particular el segmento de código (también conocido como
.text
segmento) se convirtió en solo lectura (excepto por el sistema operativo que los cargó desde el disco). Era natural que el compilador y el enlazador pusieran las cadenas literales en ese segmento de código, y las cadenas literales se volvieron de solo lectura. Cuando su programa intentó sobrescribirlos, fue malo, un comportamiento indefinido . Y tener un segmento de código de solo lectura en la memoria virtual ofrece una ventaja significativa: varios procesos que ejecutan el mismo programa comparten la misma RAM ( memoria físicapáginas) para ese segmento de código (vea elMAP_SHARED
indicador para mmap (2) en Linux).Hoy en día, los microcontroladores baratos tienen algo de memoria de solo lectura (por ejemplo, su Flash o ROM), y mantienen allí su código (y las cadenas literales y otras constantes). Y los microprocesadores reales (como el de su tableta, computadora portátil o computadora de escritorio) tienen una unidad de administración de memoria sofisticada y maquinaria de caché utilizada para memoria virtual y paginación . Por lo tanto, el segmento de código del programa ejecutable (por ejemplo, en ELF ) está mapeado en memoria como un segmento de solo lectura, compartible y ejecutable (por mmap (2) o execve (2) en Linux; por cierto, podría dar instrucciones a ldpara obtener un segmento de código grabable si realmente lo desea). Escribirlo o abusarlo generalmente es una falla de segmentación .
Por lo tanto, el estándar C es barroco: legalmente (solo por razones históricas), las cadenas literales no son
const char[]
matrices, sino solochar[]
matrices que tienen prohibido sobrescribirse.Por cierto, pocos idiomas actuales permiten que se sobrescriban los literales de cadena (incluso Ocaml, que históricamente -y mal- tenía cadenas literales de escritura ha cambiado ese comportamiento recientemente en 4.02, y ahora tiene cadenas de solo lectura).
Los compiladores actuales de C pueden optimizar y tener
"ions"
y"expressions"
compartir sus últimos 5 bytes (incluido el byte nulo de terminación).Tratar de compilar el código C en el archivo
foo.c
congcc -O -fverbose-asm -S foo.c
y mirada dentro del archivo de ensamblador generadofoo.s
por GCCPor último, la semántica de C es lo suficientemente compleja (lea más sobre CompCert y Frama-C, que están tratando de capturarlo) y agregar cadenas literales constantes y grabables lo haría aún más arcano mientras hace que los programas sean más débiles y menos seguros (y con menos comportamiento definido), por lo que es muy poco probable que los futuros estándares C acepten cadenas literales grabables. Quizás, por el contrario, los convertirían en
const char[]
matrices como deberían ser moralmente.Observe también que, por muchas razones, los datos mutables son más difíciles de manejar por la computadora (coherencia de caché), de codificar, de entender por el desarrollador, que los datos constantes. Por lo tanto, es preferible que la mayoría de sus datos (y especialmente las cadenas literales) permanezcan inmutables . Lea más sobre el paradigma de programación funcional .
En los viejos días de Fortran77 en IBM / 7094, un error incluso podía cambiar una constante: si usted
CALL FOO(1)
y siFOO
modificara su argumento pasado por referencia a 2, la implementación podría haber cambiado otras ocurrencias de 1 a 2, y eso fue realmente bicho travieso, bastante difícil de encontrar.fuente
const
estándar ( stackoverflow.com/questions/2245664/… )?1
s literales se comportan repentinamente como2
s y tan divertidos ...let 2 = 3
funcionó). Esto resultó en mucha DIVERSIÓN (en la definición de la palabra Fortaleza Enana), por supuesto. No tengo idea de cómo fue diseñado el intérprete que permitió esto, pero lo fue.Los compiladores no podían combinar
"more"
y"regex"
, ya que el primero tiene un byte nulo después de que ele
mientras que el segundo tiene unax
, pero muchos compiladores combinarían literales de cadena que han concordado perfectamente, y algunos también se correspondería con literales de cadena que compartían una cola común. El código que cambia un literal de cadena puede cambiar un literal de cadena diferente que se utiliza para un propósito completamente diferente pero que contiene los mismos caracteres.Surgiría un problema similar en FORTRAN antes de la invención de C. Los argumentos siempre se pasaban por dirección en lugar de por valor. Por lo tanto, una rutina para agregar dos números sería equivalente a:
En el caso de que uno quisiera pasar un valor constante (p. Ej., 4.0)
sum
, el compilador crearía una variable anónima y la inicializaría4.0
. Si se pasara el mismo valor a múltiples funciones, el compilador pasaría la misma dirección a todas ellas. Como consecuencia, si una función que modificó uno de sus parámetros pasó una constante de punto flotante, el valor de esa constante en otra parte del programa podría cambiar como resultado, lo que llevaría al dicho "Las variables no; las constantes no son 't ".fuente