Da página 99 de K&R:
"preferimos o último porque diz mais explicitamente que a variável é um ponteiro."
Preciso de algum esclarecimento sobre por que "dizer mais explicitamente que a variável é um ponteiro" teria alguma importância? Eu esperaria que o motivo fosse para o programa ser mais rápido, em vez de mais explícito, como declarado na página 97:
"A versão do ponteiro será, em geral, mais rápida, mas, pelo menos para os não iniciados, um pouco mais difícil de entender"
Mas nesse caso, por que a versão do ponteiro seria mais rápida? Se arr[i]
for equivalente a isso, *(a+i)
por que o programa seria mais rápido? É porque C não precisa converter o array para a versão do ponteiro?
Fora da lista de parâmetros,
char s[]
echar *s
são diferentes.Em uma lista de parâmetros,
char s[]
echar *s
são 100% equivalentes por definição.Ele está dizendo que, como
s
é um ponteiro, é mais claro usarchar *s
. Usarchar s[]
, embora equivalente, pode enganar "os não iniciados" a pensar que é um array quando não é.Note que nem todo mundo concorda em sempre usar parâmetros
char *s
em vez dechar s[]
for. Estou apenas explicando a opinião expressa pelo livro, conforme solicitado.Como
char s[]
echar *s
são equivalentes em uma lista de parâmetros, não há diferença em desempenho. Suspeito que a passagem que você citou sobre desempenho foi tirada de um contexto diferente, um ondechar s[]
echar *s
não são encontrados em uma lista de parâmetros.char *s = "abcdef";
provavelmente será mais rápidochar s[] = "abcdef";
porque o primeiro é comumente implementado como um simples carregamento de um ponteiro para uma string existente na imagem binária, enquanto o último é comumente implementado como uma cópia de 7 bytes para a pilha.Mas isso não se aplica a parâmetros. Eles são 100% equivalentes lá.
Passe algum tempo pensando na equivalência de duas versões frequentemente vistas de uma declaração de função:
Por "equivalência", para ambas as versões
argv
é, isoladamente, uma variável contendo um endereço de memória a ser interpretado como um ponteiro .Na primeira versão, o leitor entende
argv
que é um "ponteiro para um ponteiro para umchar
", mas aprendeu que ochar
armazenamento é o primeiro byte de uma string C, e que o próximo ponteiro acessível++argv
(geralmente) apontará para a próxima string C, e assim por diante.A primeira é a notação compacta, e a função provavelmente avança o ponteiro
argv
à medida que lida com cada parâmetro da linha de comando sequencialmente.Na segunda versão, a desreferenciação de
argv
é, talvez, um pouco mais explícita sobre a existência de uma matriz (deargc
elementos que são, eles próprios, ponteiros) a ser encontrada naquele caminho.A segunda sugere que, em vez de simplesmente avançar o ponteiro
argv
, pode haver uma expectativa de que a posição de cada parâmetro da linha de comando seja significativa.Por exemplo:
myrename foo bar
sabe que o arquivo a ser renomeado sempre estará emargv[1]
Como é frequentemente o caso, há muitas maneiras de esfolar um gato.
Por causa dessa equivalência, o codificador pode escolher qualquer sintaxe que forneça a maior clareza no código. Eventualmente, você verá as sintaxes como intercambiáveis, e a escolha de qual usar terá pouca consequência (quando nenhum outro problema estiver presente).
Dominar ponteiros é um dos poucos obstáculos para usar C (ou seus primos), mas é um requisito valioso para se tornar um bom programador.
Entender como arrays, sempre que usados em expressões ou escritos como parâmetros de função, são ajustados ("decaimento") em um ponteiro para o primeiro elemento é uma das coisas clássicas com as quais todos que estão aprendendo C estão tendo dificuldades. Portanto, a versão array pode ser confusa, por exemplo:
Alguns podem pensar incorretamente que isso cria uma cópia do array passado ao mostrar 10 inteiros na pilha. Mas ele é ajustado para um ponteiro do primeiro elemento do array passado, e então é equivalente a
int* array
.A citação completa e o contexto de K&R 2ª edição:
Em primeiro lugar, a subscrição de arrays sempre envolve decaimento de arrays, então ela não pode ser usada com arrays; ela é sempre usada com ponteiros (decaídos).
"A versão do ponteiro será em geral mais rápida" é simplesmente um absurdo total. Não há como nem mesmo o fanboy mais dogmático de K&R varrer essa declaração descaradamente incorreta para debaixo do tapete e salvar a cara dos autores.
Compre um livro melhor, de preferência deste milênio.
Aqui está um pouco mais de contexto para a citação sobre a versão do ponteiro ser mais rápida:
E, algumas páginas depois:
[Meus números de página não correspondem, então devemos estar lendo edições diferentes.]
Se você tem um nome de array e quer desreferenciar para um offset específico, primeiro você tem que (1) carregar o endereço do array em um registrador e (2) fazer a aritmética para chegar ao offset. Se você já tem um ponteiro para o primeiro elemento, então você efetivamente já fez o primeiro passo.
Isso não importa para acessos únicos, mas pode ter importado para um algoritmo que faz acessos repetidos, seja sequencialmente ou saltando.
Mas os detalhes são específicos para o hardware e o compilador, ambos os quais evoluíram muito desde que o escreveram. Ao longo dos anos, vi demonstrações comparando uma versão de indexação de array e uma versão de desreferenciação de ponteiro do mesmo algoritmo. Às vezes, a mais rápida era indexada, às vezes era por ponteiro. Conforme os anos passavam, a diferença era geralmente cada vez menos significativa.
Ambos os métodos são válidos, e hoje em dia geralmente não há diferença de desempenho. É importante entender ambos. Mas ao decidir qual usar, use aquele que melhor expressa o algoritmo. A preferência de K&R pela notação de ponteiro é uma tentativa de lembrar ao leitor que a decadência de ponteiro acontece, e você pode ficar confuso se fingir que não acontece.