paulo1205
(usa Ubuntu)
Enviado em 27/06/2013 - 16:43h
Porque o compilador do Dev-C++ é velho, não atendendo ao padrão do C++. Antigamente essa construção de atribuir uma constante literal a um ponteiro não qualificado com
const, que ainda funciona em C, funcionava também na maioria das implementações pré-padrão do C++.
Se você atualizar o compilador usado pelo Dev-C++ (deve dar para fazer isso de alguma forma, afinal o Dev-C++ é só um front-end para o GCC) provavelmente vai ter os mesmos erros.
Veja porém que a forma de corrigir é fazer o ponteiro ser mesmo um ponteiro para dados constantes. Mesmo em C, embora seja legal fazer a tal atribuição, é ilegal depois usar esse ponteiro para escrever na área atribuída, mas o compilador não tem como detectar isso nem lhe informar. Abaixo segue o extrato de uma sessão que demonstra isso de modo bem claro.
$ cat z.c
char str[]="Teste";
char *s="Saulo";
int main(void){
str[0]='P';
s[0]=str[0];
return 0;
}
O programa é bem simples: declara um array (não constante) e um ponteiro para dados não constantes
s, mas que é feito apontar para uma constante, e depois tenta alterar os primeiros elementos do array e do ponteiro.
$ gcc -Wall -Werror -O2 -std=c99 -pedantic -g z.c -o z
Compila-se o programa com o máximo de opções de diagnósticos ligadas, e também com opções para embutir no código executável informações de depuração. Mesmo com todas as opções de diagnóstico, não vem nenhuma mensagem de erro.
Apesar da diferença intrínseca de tipos de dados entre o ponteiro para dados não-constante e a constante para a qual ele aponta, o padrão do C determina -- por questões de compatibilidade com código antigo -- que tal tipo de atribuição deve ser aceita no caso específico de constantes strings. Então essa atribuição não provoca reclamação.
A linha seguinte (
s[0]=str[0];) também não pode provocar reclamação, já que é perfeitamente válido mudar um dado apontado por um ponteiro que não é qualificado como constante.
$ ./z
Segmentation fault (core dumped)
O programa é executado, mas dá pau (falha de segmentação, que geralmente indica acesso ilegal à memória). Uma cópia da memória no momento da falha é gravada no arquivo "core".
$ echo bt | gdb z core
GNU gdb (Ubuntu/Linaro 7.4-2012.02-0ubuntu2) 7.4-2012.02
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
For bug reporting instructions, please see:
<http://bugs.launchpad.net/gdb-linaro/>...
Reading symbols from /tmp/tmp/z...done.
[New LWP 8262]
warning: Can't read pathname for load map: Input/output error.
Core was generated by `./z'.
Program terminated with signal 11, Segmentation fault.
#0 main () at z.c:6
6 s[0]=str[0];
(gdb) #0 main () at z.c:6
(gdb) quit
Para ver exatamente onde deu pau, chama-se o depurador (gdb) sobre o programa z, pedindo que ele use como imagem de memória o arquivo core, gerado acima. Passa-se ao gdb o comando "bt", que faz com que ele imprima um
back-trace da execução.
Veja na saída do gdb que ele realmente indica que o programa saiu com um o sinal 11 (SIGSEGV), e na impressão do back-trace aponta-se justamente a linha em que se tenta alterar o primeiro caráter da string. Por quê?
$ objdump -j .data -j .rodata -s z
z: file format elf64-x86-64
Contents of section .rodata:
4005b8 01000200 5361756c 6f00 ....Saulo.
Contents of section .data:
601008 00000000 00000000 00000000 00000000 ................
601018 bc054000 00000000 54657374 65000000 ..@.....Teste...
Roda-se o comando objdump, pedindo-se que ele imprima o conteúdo das seções ".rodata" (de
read-only data), bem como ".data" (dados comuns, com acesso de escrita). Veja onde está cada uma das strings.
Por que "Saulo" está como read-only, e "Teste" como dado alterável? Porque "Teste" é parte da definição de um array, dispondo seus seis bytes (cinco letras mais o byte nulo) ao longo da memória alocada específicamente para aquele array. E como o array não é declarado como constante, então seu conteúdo deve ser disposto numa região de memória que possa ser modificada (se ele fosso constante, também iria parar em ".rodata").
Já os seis bytes de "Saulo" (também cinco letras mais o byte nulo) são uma constante literal do programa.
s é uma variável de tipo ponteiro, que pode, ao longo do programa, apontar para qualquer lugar. Seu vínculo com a constante "Saulo" é, do ponto de vista do compilador, apenas uma situação casual.
Mas por que o compilador insiste que uma constante literal seja marcada como
read-only?
Além da resposta óbvia embutida na palavra "constante", existem motivos de ordem prática para tanto. Uma possibilidade de implementação --que, de fato, a maioria dos compiladores pratica -- é identificar constantes repetidas ao longo do programa, e eventualmente fazer com que elas apontem para a mesma região de memória. Numa implementação assim, alterar uma das constantes possívelmente faria com que todas as outras constantes idênticas fossem também alteradas, mesmo sem que isso fique explícito para o usuário.
Veja o seguinte programa.
#include <stdio.h>
int main(void){
char *s="Saulo";
s[0]='P';
printf("%s\n", s);
printf("%s\n", "Saulo");
return 0;
}
Ele vai compilar, mas não vai rodar. O que me interessa é a saída do objdump, para demonstrar que o compilador aglutinou as quatro strings em apenas duas.
objdump -s -j .rodata -j .data x
x: file format elf64-x86-64
Contents of section .rodata:
400658 01000200 5361756c 6f002573 0a00 ....Saulo.%s..
Contents of section .data:
601010 00000000 00000000 00000000 00000000 ................
Veja o que vai acontecer se eu embutir no programa um código de baixo nível que altera as permissões do segmento onde está contida a string "Saulo", de modo a habilitar nela a possibilidade de alterá-la.
$ cat x.c
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/mman.h>
char *s="Saulo";
int main(void){
size_t pg_size=sysconf(_SC_PAGE_SIZE);
void *pg_start=(void *)((long)s & ~(pg_size-1));
mprotect(pg_start, pg_size, PROT_READ|PROT_WRITE|PROT_EXEC);
s[0]='P';
printf("%s\n", s);
printf("%s\n", "Saulo");
return 0;
}
$ gcc -Wall -Werror -std=c99 -pedantic -O2 x.c -o x
$ objdump -s -j .rodata x
x: file format elf64-x86-64
Contents of section .rodata:
400708 01000200 25730a00 5361756c 6f00 ....%s..Saulo.
$ ./x
Paulo
Paulo
Ou seja, forçar a mão para alterar uma string referida (temporariamente!) por uma variável ponteiro acabou alterando outra string que é uma constante do programa.