Que vantagem (s) dos literais de cadeia de caracteres sendo somente leitura justificam (-ies / -ied):
Mais uma maneira de dar um tiro no próprio pé
char *foo = "bar"; foo[0] = 'd'; /* SEGFAULT */
Incapacidade de inicializar com elegância uma matriz de leitura e gravação de palavras em uma linha:
char *foo[] = { "bar", "baz", "running out of traditional placeholder names" }; foo[1][2] = 'n'; /* SEGFAULT */
Complicando o próprio idioma.
char *foo = "bar"; char var[] = "baz"; some_func(foo); /* VERY DANGEROUS! */ some_func(var); /* LESS DANGEROUS! */
Economizando memória? Eu li em algum lugar (não consegui encontrar a fonte agora) que, há muito tempo, quando a RAM era escassa, os compiladores tentavam otimizar o uso da memória, mesclando seqüências semelhantes.
Por exemplo, "mais" e "regex" se tornariam "moregex". Isso ainda é verdade hoje, na era dos filmes digitais com qualidade blu-ray? Entendo que os sistemas embarcados ainda operam em um ambiente de recursos restritos, mas a quantidade de memória disponível aumentou dramaticamente.
Problemas de compatibilidade? Presumo que um programa legado que tentasse acessar a memória somente leitura travasse ou continuasse com um bug não descoberto. Portanto, nenhum programa legado deve tentar acessar literal de string e, portanto, permitir a gravação em literal de string não prejudicaria programas legados portáteis válidos, não hackeados .
Existem outras razões? Meu raciocínio está incorreto? Seria razoável considerar uma alteração nos literais das cadeias de leitura e gravação nos novos padrões C ou pelo menos adicionar uma opção ao compilador? Isso foi considerado antes ou meus "problemas" são muito pequenos e insignificantes para incomodar alguém?
Respostas:
Historicamente (talvez reescrevendo partes dela), era o contrário. Nos primeiros computadores do início dos anos 1970 (talvez PDP-11 ) executando um C embrionário prototípico (talvez BCPL ), não havia MMU e nenhuma proteção de memória (que existia na maioria dos mainframes IBM / 360 mais antigos ). Portanto, cada byte de memória (incluindo aqueles que manipulam cadeias literais ou código de máquina) pode ser sobrescrito por um programa incorreto (imagine um programa mudando alguns
%
para/
uma cadeia de formato printf (3) ). Portanto, cadeias literais e constantes eram graváveis.Quando adolescente, em 1975, eu codifiquei no museu Palais de la Découverte em Paris em computadores antigos da década de 1960 sem proteção de memória: o IBM / 1620 tinha apenas uma memória principal - que era possível inicializar através do teclado, e era preciso digitar várias dezenas de dígitos para ler o programa inicial em fitas perfuradas; CAB / 500 tinha uma memória de tambor magnético; você pode desativar a gravação de algumas faixas através de interruptores mecânicos próximos ao tambor.
Mais tarde, os computadores obtiveram algum tipo de unidade de gerenciamento de memória (MMU) com alguma proteção de memória. Havia um dispositivo que proibia a CPU de substituir algum tipo de memória. Portanto, alguns segmentos de memória, principalmente o segmento de código (também conhecido como
.text
segmento), tornaram-se somente leitura (exceto pelo sistema operacional que os carregou do disco). Era natural que o compilador e o vinculador colocassem as cadeias literais nesse segmento de código, e as cadeias literais se tornassem somente leitura. Quando seu programa tentou substituí-los, foi um comportamento indefinido . E ter um segmento de código somente leitura na memória virtual oferece uma vantagem significativa: vários processos executando o mesmo programa compartilham a mesma RAM ( memória físicapáginas) para esse segmento de código (consulte oMAP_SHARED
sinalizador para mmap (2) no Linux).Hoje, os microcontroladores baratos têm alguma memória somente leitura (por exemplo, Flash ou ROM) e mantêm seu código (e as strings literais e outras constantes) lá. E microprocessadores reais (como o do seu tablet, laptop ou desktop) possuem uma sofisticada unidade de gerenciamento de memória e máquinas de cache usadas para memória virtual e paginação . Portanto, o segmento de código do programa executável (por exemplo, no ELF ) é mapeado como um segmento somente leitura, compartilhável e executável (por mmap (2) ou execve (2) no Linux; BTW, você pode fornecer diretrizes para ldpara obter um segmento de código gravável, se você realmente quisesse). Escrever ou abusar geralmente é uma falha de segmentação .
Portanto, o padrão C é barroco: legalmente (apenas por razões históricas), cadeias literais não são
const char[]
matrizes, mas somentechar[]
matrizes que são proibidas de serem substituídas.BTW, poucas linguagens atuais permitem que os literais de strings sejam substituídos (mesmo o Ocaml, que historicamente - e mal - possuía strings literais graváveis, mudou esse comportamento recentemente em 4.02, e agora possui strings somente leitura).
Os compiladores C atuais podem otimizar, compartilhar
"ions"
e"expressions"
compartilhar seus últimos 5 bytes (incluindo o byte nulo final).Tente compilar o código C em arquivo
foo.c
comgcc -O -fverbose-asm -S foo.c
e olhar dentro do arquivo assembler geradofoo.s
pelo GCCPor fim, a semântica de C é complexa o suficiente (leia mais sobre o CompCert e o Frama-C, que estão tentando capturá-lo) e a adição de seqüências literais constantes e graváveis tornaria ainda mais misteriosa, tornando os programas mais fracos e menos seguros (e com menos comportamento definido), portanto, é muito improvável que os futuros padrões C aceitem cadeias literais graváveis. Talvez, pelo contrário, eles os fizessem
const char[]
matrizes como deveriam ser moralmente.Observe também que, por muitas razões, os dados mutáveis são mais difíceis de manipular pelo computador (coerência do cache), codificar, entender pelo desenvolvedor, do que dados constantes. Portanto, é preferível que a maioria dos seus dados (e principalmente as cadeias literais) permaneçam imutáveis . Leia mais sobre o paradigma de programação funcional .
Nos velhos Fortran77 dias na IBM / 7094, um erro poderia até mesmo mudar uma constante: se você
CALL FOO(1)
e seFOO
aconteceu para modificar seu argumento passado por referência a 2, a implementação pode ter alterado outras ocorrências de 1 para 2, e que foi um realmente bug impertinente, bastante difícil de encontrar.fonte
const
padrão ( stackoverflow.com/questions/2245664/… )?1
s repente se comportam como2
s e tão divertido ...let 2 = 3
funcionava). Isso resultou em muita diversão (na definição da palavra Dwarf Fortress), é claro. Não tenho idéia de como o intérprete foi projetado para permitir isso, mas foi.Os compiladores não puderam combinar
"more"
e"regex"
, porque o primeiro possui um byte nulo após oe
segundox
, mas muitos compiladores combinavam literais de strings que correspondiam perfeitamente, e alguns também combinavam literais de strings que compartilhavam uma cauda comum. O código que altera um literal de cadeia de caracteres pode, portanto, alterar um literal de cadeia de caracteres diferente, usado para algum propósito totalmente diferente, mas que contém os mesmos caracteres.Uma questão semelhante surgiria no FORTRAN antes da invenção de C. Os argumentos sempre eram passados por endereço e não por valor. Uma rotina para adicionar dois números seria assim equivalente a:
No caso de alguém querer passar um valor constante (por exemplo, 4.0) para
sum
, o compilador criaria uma variável anônima e a inicializaria4.0
. Se o mesmo valor fosse passado para várias funções, o compilador passaria o mesmo endereço para todas elas. Como conseqüência, se uma função que modificou um de seus parâmetros recebeu uma constante de ponto flutuante, o valor dessa constante em outro local do programa pode ser alterado como resultado, levando ao ditado "Variáveis não; constantes não são 't ".fonte