paulo1205
(usa Ubuntu)
Enviado em 12/12/2018 - 18:44h
Um comentário adicional (baseado em opinião minha).
A “aritmética de ponteiros” perdeu relevância na medida em que os programadores em C passaram a adotar práticas de programação mais seguras, e que os próprios compiladores passaram a reclamar mais de práticas antigas e que hoje são consideradas inseguras e perigosas.
Essa discussão era muito mais importante, e alvo de confusão, numa época — que já vai remota — em que ponteiros e variáveis inteiras eram usadas de modo intercambiável. Ninguém em são consciência faz isso gratuitamente hoje em dia (e, quando o faz, geralmente cerca o código dos devidos cuidados sintáticos e visuais).
O problema acontecia no passado em código semelhante ao que vai abaixo, que penso que pode ilustrar o problema.
id(p){ return p; }
main(){
int v[]={1, 2, 3};
int *a, *b, *c, *d;
a=v+1; /* Aritmética de ponteiro. OK. */
b=id(v)+1; /* Ponteiro se perde. Aritmética com dois inteiros. */
c=id(v+1); /* Aritmética de ponteiros antes da função. Depois o ponteiro se perde. */
d=id(v)+sizeof *v; /* Ponteiro se perde. Valor certo do endereço obtido manualmente. */
printf("%d %d %d %d %d\n", &v[1], a, b, c, d);
printf("%d %d %d %d %d\n", v[1], *a, *b, *c, *d);
}
Esse código é obsoleto de várias maneiras mas, em particular, a função
id(), que supostamente retorna a mesma coisa que recebe como argumento, não especifica nem o tipo de dados do argumento nem o tipo do dado retornado. Nesse caso, o que acontece é que ambos são assumidos como sendo
int. E como, em muitas máquinas antigas (e ainda algumas máquinas atuais), frequentemente
int e ponteiros têm a mesma representação interna (que tipicamente coincidia com o tipo dos registradores de uso comum no processador), essa coincidência era assumida como líquida e certa e até como desejável. O compilador não reclamava, e todos eram felizes.
Exceto quando tinha que acontecer alguma operação aritmética com os dados. Com
arrays e ponteiros, somar 1 (ou
n) significa ir para o próximo elemento (ou para
n posições depois do início) e, nos casos em que o tamanho de cada elemento é maior do que um
byte, isso provocava um deslocamento diferente do que ocorreria com a soma pura entre dois inteiros.
Os cuidados com a aritmética de ponteiros eram, então, uma abordagem para lembrar ao usuário que, embora ponteiros e inteiros pudessem ser usados de modo intercambiável em geral, ele deveria ter cuidado com suas operações se um dos operandos fosse um ponteiro, porque o resultado poderia ser diferente do que ele esperaria em uma soma com dois inteiros. Ao mesmo tempo, ele deveria lembrar também que se ele precisasse operar com um inteiro que viesse a se tornar ponteiro no futuro, os deslocamentos teriam de ser multiplicados pelos tamanhos dos dados que viessem a ser apontados, caso contrário, ele poderia acabar ficando com um valor que, quando usado como endereço (ponteiro), seria inválido.
Veja alguns resultados, num PC com 32 e com 64 bits (o PC aceita endereçar inteiros em endereços que não sejam múltiplos de
sizeof(int)), numa SUN UltraSparc em 32 e 64 bits (que não aceita endereços que não sejam múltiplos do tamanho do dado) e num IBM Power em 32 e 64 bits (que também aceita dados em endereços que não são múltiplos do tamanho do dado).
$ ./linuxpc32 ; ./linuxpc64
-3798332 -3798332 -3798335 -3798332 -3798332
2 2 33554432 2 2
-755092352 -755092352 -755092355 -755092352 -755092352
Segmentation fault (core dumped)
$ ./solaris32 ; ./solaris64
-4195232 -4195232 -4195235 -4195232 -4195232
Bus Error (core dumped)
2147482408 2147482408 2147482405 2147482408 2147482408
Bus Error (core dumped)
$ ./aix32 ; ./aix64
804398476 804398476 804398473 804398476 804398476
2 2 256 2 2
-2972 -2972 -2975 -2972 -2972
Segmentation fault (core dumped)
No PC e no Power, as versões em 32 bits funcionaram de modo parecido: como inteiros e ponteiros têm o mesmo tamanho, o programa rodou até o fim, mas produziu endereços diferentes para o ponteiro
b de todos os demais, e
d só ficou com o valor certo porque fizemos a computação manual. Na impressão dos valores, o obtido através de
b ficou diferente dos demais (no PC, cujos dados são com byte menos significativos primeiro, o valor impresso foi o equivalente a 0x02000000, onde o “02” é o primeiro byte do segundo elemento, e os três “00” são os três últimos bytes do primeiro; no AIX, em que o byte mais significativo vem primeiro, o valor impresso foi o equivalente a 0x00000100, em que “000001” corresponde aos três últimos bytes do primeiro elemento, e o “00” vem do primeiro byte do segundo elemento). No processador Sparc, dados não-alinhados provocam erro de barramento, de modo que nenhum dado foi impresso (o programa capota ao tentar fazê-lo), mas apenas os endereços (antes de capotar), com discrepâncias entre si semelhantes às vistas no PC e no Power.
Em 64 bits, acontece um problema maior: inteiros e ponteiros não têm mais tamanhos coincidentes (inteiros têm 32 bits, ponteiros têm 64 bits), de modo que possivelmente a conversão de ponteiro para inteiro provoca truncamento de informação, e a conversão de inteiros para ponteiros pode produzir endereços completamente inválidos para o programa. Por isso, PC e Power caíram com falha de segmentação (endereços inválidos após as conversões). No Sparc, novamente tivemos erro de barramento — possivelmente, por uma fortuita coincidência, o endereço não usava os bits de 33 em diante, cabendo nos 32 bits. Mas embora o endereço não tenha sido perdido, o valor calculado para
b, sem aritmética de ponteiro, continua desalinhado, e portanto inválido.
A antiga coincidência de representação interna entre ponteiros e inteiros induzia a seu uso intercambiável, e as pessoas eram instadas a prestar atenção no contexto para saber qual tipo de aritmética usar. Com a mudança de tecnologia, que fez mais comuns ponteiros e inteiros que não são mais intercambiáveis, a própria linguagem passou a ser menos tolerante com o uso intercambiável. Isso levou o uso intercambiável a ficar mais e mais raro com o tempo. Hoje, só alguém que parou no tempo, que ainda esteja usando livros obsoletos e compiladores da década de 1990 (infelizmente ainda existe, especialmente em faculdades brasileiras do tipo Zé-das-Couves) é que ainda confunde ponteiros com inteiros. A aritmética de ponteiros é hoje mais uma curiosidade que só aparece quando você faz força para ver “como é um ponteiro por dentro” do que uma preocupação do dia-a-dia.
E isso me faz ficar curioso: por que você fez a pergunta?
----
Recomendo a leitura de um artigo sobre a história do C, desde priscas eras, escrito por seu próprio criador, Dennis M. Ritchie (já falecido):
https://www.bell-labs.com/usr/dmr/www/chist.html.