paulo1205
(usa Ubuntu)
Enviado em 04/01/2015 - 07:18h
Caro Enzo,
Camarada, eu não que raios deu em mim, mas eu viajei na maionese quando falei da conversão entre ponteiros e inteiros. Quero dizer: o que eu disse está tecnicamente certo, só que você não usou nada disso no seu programa, pois todos os seus
casts foram exclusivamente entre ponteiros.
Peço desculpas por ter falado demais e tão apaixonadamente sobre um erro que você não cometeu.
EnzoFerber escreveu:
( ... )
O pulo do gato é que ponteiros são apenas endereços, nada mais.
Do ponto de vista estritamente teórico (ou de programador Assembly), eu concordo com essa colocação.
Entretanto, no contexto da linguagem C, ponteiros carregam, durante a fase de compilação, informação sobre os tipos de dados apontados. Desse modo, eles não são "apenas endereços". São endereços, sim, mas qualificados quanto aos dados que pode haver nesses endereços. Aliás, essa é uma característica essencial para o uso do operador
* ("conteúdo de").
A exceção a essa qualificação, que acaba se parecendo mais com ponteiros teóricos puros, são ponteiros do tipo
void *, que são usados em C quando deliberadamente não se quer levar em consideração ou não se pode prever o tipo de dado apontado. Por sinal, o operador
* não pode ser usado diretamente sobre tais ponteiros.
Então, se você souber o conteúdo do endereço e como este está organizado, você pode manipulá-lo da forma como quiser. Foi isso o que fiz com a conversão (char []) -> (int*).
O que eu disse ao computador foi:
"No endereço apontado por s tem uma sequencia de informações que devem ser lidas como um número inteiro."
E isso foi exatamente o que o programa fez.
Eis a questão: não foi bem isso o que você expressou, e o seu programa só vez o que você gostaria que ele fizesse por coincidência (daquelas que, por esconder erros, eu costumo chamar de azar, e não de sorte). Vou tentar explicar novamente, quiçá de modo mais fácil de entender.
Permita-me começar com um comentário a respeito de notação. Eu entendi perfeitamente que, ao dizer "
char []", você quis dizer "array de caracteres de um tamanho qualquer" e sei que você sabe que o compilador automaticamente calculou a quantidade de elementos desse array como 4 por causa da presença da lista de inicialização de elementos na hora em que ele foi declarado. Mesmo assim, eu prefiro ser explícito num contexto como este, de explicação, porque existe um caso em que o compilador interpreta colchetes vazios (ou mesmo preenchidos) como se fosse uma declaração de ponteiro absolutamente sinônima de "
char *" (a saber: na declaração de parâmetro de função -- uma reminiscência da linguagem B e das primeiríssimas versões de C, que nem mesmo possuíam a notação com asteriscos, usando sempre colchetes vazios tanto para arrays como para ponteiros). Por isso, eu prefeiro ser explícito, e dizer que o tipo de
s é "
char [4]".
O segundo ponto é sua coerção de tipos não foi de "
char []", "
char [4]" ou mesmo de "
char *" para "
int *", mas sim de "
char (*)[4]" (ponteiro para array com quatro elementos do tipo
char) para "
int *", pois você usou indevidamente o operador
& sobre o array
s. Sua afirmação de agora confirmou a suposição que eu fiz, ao escrever que o contexto e a função deixavam claro que você queria usar apenas "
s" em todos os lugares em que escreveu "
&s".
Em outras palavras -- e usando suas próprias palavras -- você não disse "[no] endereço apontado por
s" porque, sendo
s um nome de array, o endereço por ele apontado é dado pelo seu próprio nome, sem nenhum operador. Quando você aplicou
& sobre ele, você transformou sua frase em "no endereço
ocupado por s" (faça a prova real: imagine a declaração "
int i;" e me diga o que significa a expressão "
&i").
Mas como, então, seu programa funcionou? Funcionou porque você, apesar de não ter se dado conta na hora (nem em sua última mensagem, dada a explicação que você trouxe), esbarrou coincidentemente numa idiossincrasia do C. Como o endereço inicial de um array, que é denotado por seu nome, não ocupa lugar na memória (pois é transformado numa constante durante a compilação), o operador
&, ao ser aplicado sobre o nome do array, não devolve o endereço
ocupado pelo nome -- pois ele não ocupa endereço algum --, mas sim
o próprio valor representado por aquele nome (que é um endereço: justamente o do primeiro elemento do array), mudando, no entanto, o tipo associado a tal valor de "array com N elementos do tipo X" para "ponteiro para array com N elementos do tipo X".
( ... )
Novamente, reitero: seus comentários são muito pertinentes e e todo cuidado é pouco ao manipulador ponteiros. Mas discordo que o que demostrei no programa expresse uma falha ou fraqueza da linguagem, muito pelo contrário. Pra mim, a capacidade de manipular a memória em um nível quase tão baixo quanto Assembly torna C uma linguagem muito poderosa. (Lembro sempre do Tio Ben conversando com o Peter Parker...)
Parece-me que essa resposta (e toda aquela curiosidade sobre endianness, que eu removi agora porque não vem ao caso) se deve a eu ter dito que "[
i]nfelizmente, a linguagem não ajuda".
Existem dois tipos de "a linguagem não ajuda". O primeiro tipo é o da linguagem que é tão deficiente que dificulta o trabalho (uma leitura interessante que serve de exemplo, embora descreva uma linguagem que ninguém mais usa na forma como existia então, é a do artigo "Why Pascal is not my favorite Programming Language", escrito em 1981 por Brian Kernighan, o "K" da dupla "K&R"). O outro tipo é quando a linguagem é tão permissiva que fica fácil cometer erros crassos sem que o compilador ajude a identificá-los, e algumas construções da linguagem podem até mesmo induzir ao erro ou ao seu mascaramento. Pior ainda, identificar tais erros, para poder corrigi-los, acaba se tornando uma tarefa mais difícil.
C tem exemplos dos dois tipos.
Um exemplo do primeiro caso é o suporte da linguagem a strings, que se limita em aceitar a sintaxe de constantes literais entre aspas, que a faz dispor os caracteres não-nulos em ordem na memória e os sucede com um caráter nulo. Todo o resto, incluindo as funções que podem lidar com essas sequências de
N não-nulos+1 nulo está fora do núcleo da linguagem, residindo apenas na biblioteca. Isso tem óbvias vantagens para quem implementa a linguagem mas, para quem meramente a utiliza, na melhor das hipóteses não traz ganho nenhum, e na prática acaba criando limitações injustificáveis teoricamente (como, por exemplo, não poder ter um caráter nulo no meio do string).
Curiosamente, o suporte a strings acaba sendo, ao mesmo tempo, um exemplo do segundo caso. Na linguagem, além da indistinção entre arrays que representam strings e os que não os representam, residem também algumas pegadinhas, tais como a de permitir que uma constante literal entre aspas, que é, como a própria designação diz, uma
constante, seja atribuída a um array ou ponteiro que não precisa ser declarado como apontando para dados constantes (que é, por sinal, o uso mais comum) e que pode, portanto, ser utilizado como argumento na chamada a uma função que pode tentar modificar o conteúdo, com consequências imprevisíveis (com um pouco de sorte, o programa capota, mesmo tendo compilado sem erros, no primeiro uso; se for azarado, aquela constante deixa de ser constante, posto que é alterada, e sai afetando coisas indesejadas ao longo da execução do programa).
Digo essas coisas porque odeio C? Não, ou eu teria de ser muito masoquista por trabalhar com ela há quase 27 anos (desde meados de 1988) e ainda participar de fóruns como este (se bem que dizem que o ódio é um companheiro mais fiel do que o amor... ;) ). Por ignorância? Também não, e espero que as colocações que faço deixem claro que eu falo com conhecimento de causa (aliás, muitos, provavelmente a vasta maioria, dos erros que eu aponto são erros que eu cansei de cometer, e sobre os quais só aprendi com o tempo).
Quando eu critico a possibilidade de, primeiro, usar o operador "endereço de" (
&) sobre um ente que não possui endereço próprio, e, segundo, tal operação dar como resultado o endereço de outro objeto, só que com um tipo dado diferente do desse outro objeto, faço-o porque entendo que o primeiro ponto cria uma inconsistência ("por que para alguns entes constantes pode, e para outros não?"), e porque o segundo, além de pouquíssimo útil (nesses 27 anos, nunca vi ser usado devidamente, mas já cansei de ver ser indevidamente empregado, tanto por desatenção como por desconhcimento), cria espaço para erros fáceis de cometer e difíceis de diagnosticar.
No seu caso, não fez muita diferença porque você acabou fazendo um
cast e, por isso, tanto fazia o se o valor do endereço vinha do array ou de um ponteiro para o array, dado que ambos são numericamente iguais (e eis um dos perigos também dos
casts: achar que se sabe o que se está fazendo, quando na verdade não se sabe). Mas imagine se, em lugar (ou mesmo antes) do
cast, você fizesse alguma aritmética com esse endereço. Uma coisa é somar
n à base de um array, e outra é somar esse mesmo
n a um ponteiro para um array inteiro de uma vez.
Não obstante toda essa prosa, saiba que C segue sendo uma de minhas ferramentas de trabalho favoritas. E é por isso que estamos aqui!