Como Joel aponta no podcast Stack Overflow # 34 , na linguagem de programação C (também conhecida como: K & R), há menção dessa propriedade de matrizes em C:a[5] == 5[a]
Joel diz que é por causa da aritmética dos ponteiros, mas eu ainda não entendo. Por que faza[5] == 5[a]
?
c
arrays
pointers
pointer-arithmetic
Dinah
fonte
fonte
a[1]
como uma série de tokens, não seqüências de caracteres: * ({local inteiro de} a {operador} + {inteiro} 1) é o mesmo que * ({inteiro} 1 {operador} + {local inteiro de} a) mas não é o mesmo que * ({local inteiro de} a {operator} + {operator} +)char bar[]; int foo[];
efoo[i][bar]
é usado como expressão.a[b]
=*(a + b)
para qualquer dadoa
eb
, mas era a escolha livre dos designers de linguagem+
para serem definidos como comutativos para todos os tipos. Nada poderia impedi-los de proibiri + p
enquanto permitiamp + i
.+
ser comutativo, então talvez o verdadeiro problema seja optar por fazer as operações dos ponteiros parecerem aritméticas, em vez de projetar um operador de deslocamento separado.Respostas:
O padrão C define o
[]
operador da seguinte maneira:a[b] == *(a + b)
Portanto
a[5]
, avaliará para:e
5[a]
avaliará para:a
é um ponteiro para o primeiro elemento da matriz.a[5]
é o valor que está a 5 elementos mais adiantea
, que é o mesmo que*(a + 5)
, e da matemática do ensino fundamental sabemos que esses são iguais (a adição é comutativa ).fonte
a[5]
será compilado para algo como emmov eax, [ebx+20]
vez de[ebx+5]
*(10 + (int *)13) != *((int *)10 + 13)
. Em outras palavras, há mais coisas acontecendo aqui do que a aritmética da escola primária. A comutatividade depende criticamente do compilador, reconhecendo qual operando é um ponteiro (e para qual tamanho do objeto). Em outras palavras(1 apple + 2 oranges) = (2 oranges + 1 apple)
, mas(1 apple + 2 oranges) != (1 orange + 2 apples)
.Como o acesso ao array é definido em termos de ponteiros.
a[i]
é definido para significar*(a + i)
, que é comutativo.fonte
*(i + a)
, que pode ser escrito comoi[a]
".*(a + i)
é comutativo". No entanto,*(a + i) = *(i + a) = i[a]
porque a adição é comutativa.Eu acho que algo está faltando nas outras respostas.
Sim,
p[i]
por definição é equivalente a*(p+i)
, o qual (porque a adição é comutativa) é equivalente a*(i+p)
, ao qual (novamente, pela definição do[]
operador) é equivalente ai[p]
.(E
array[i]
, em , o nome da matriz é implicitamente convertido em um ponteiro para o primeiro elemento da matriz.)Mas a comutatividade da adição não é tão óbvia nesse caso.
Quando ambos os operandos são do mesmo tipo, ou mesmo de diferentes tipos numéricos que são promovidos a um tipo comum, comutatividade faz todo o sentido:
x + y == y + x
.Mas, neste caso, estamos falando especificamente sobre aritmética de ponteiros, em que um operando é um ponteiro e o outro é um número inteiro. (Inteiro + inteiro é uma operação diferente, e ponteiro + ponteiro é um disparate.)
A descrição do
+
operador da norma C ( N1570 6.5.6) diz:Poderia facilmente ter dito:
nesse caso, ambos
i + p
ei[p]
seria ilegal.Em termos de C ++, realmente temos dois conjuntos de
+
operadores sobrecarregados , que podem ser descritos livremente como:e
dos quais apenas o primeiro é realmente necessário.
Então, por que é assim?
O C ++ herdou essa definição de C, que a obteve de B (a comutatividade da indexação de matriz é explicitamente mencionada na Referência do Usuário de 1972 para B ), que a obteve de BCPL (manual de 1967), que pode muito bem ter sido obtida até idiomas anteriores (CPL? Algol?).
Portanto, a ideia de que a indexação de array é definida em termos de adição, e essa adição, mesmo de ponteiro e número inteiro, é comutativa, remonta há muitas décadas aos idiomas ancestrais de C.
Essas línguas eram muito menos tipificadas do que o C moderno. Em particular, a distinção entre ponteiros e números inteiros era frequentemente ignorada. (Os programadores C iniciais às vezes usavam ponteiros como números inteiros não assinados, antes que a
unsigned
palavra - chave fosse adicionada ao idioma.) Portanto, a idéia de tornar a adição não comutativa porque os operandos são de tipos diferentes provavelmente não teria ocorrido aos projetistas dessas linguagens. Se um usuário quiser adicionar duas "coisas", sejam elas números inteiros, ponteiros ou qualquer outra coisa, não cabe ao idioma impedi-lo.E, ao longo dos anos, qualquer alteração a essa regra quebraria o código existente (embora o padrão ANSI C de 1989 possa ter sido uma boa oportunidade).
Alterar C e / ou C ++ para exigir colocar o ponteiro à esquerda e o número inteiro à direita pode quebrar algum código existente, mas não haveria perda de potência expressiva real.
Portanto, agora temos
arr[3]
e3[arr]
significamos exatamente a mesma coisa, embora a última forma nunca deva aparecer fora da IOCCC .fonte
3[arr]
é um artefato interessante, mas que raramente deve ser usado. A resposta aceita para esta pergunta (< stackoverflow.com/q/1390365/356> ), que perguntei há algum tempo, mudou a maneira como pensava sobre a sintaxe. Embora geralmente não exista uma maneira certa e errada de fazer essas coisas, esses tipos de recursos fazem você pensar de uma maneira separada dos detalhes da implementação. Há um benefício nessa maneira diferente de pensar, que em parte se perde quando você se fixa nos detalhes da implementação.ring16_t
que contenha 65535 produziria aring16_t
com valor 1, independente do tamanho deint
.E claro
A principal razão para isso foi que, nos anos 70, quando o C foi projetado, os computadores não tinham muita memória (64 KB eram muito); portanto, o compilador C não fazia muita verificação de sintaxe. Portanto, "
X[Y]
" foi traduzido cegamente para "*(X+Y)
"Isso também explica as sintaxes "
+=
" e "++
". Tudo na forma "A = B + C
" tinha a mesma forma compilada. Mas, se B fosse o mesmo objeto que A, uma otimização no nível de montagem estava disponível. Mas o compilador não era brilhante o suficiente para reconhecê-lo, então o desenvolvedor teve que (A += C
). Da mesma forma, seC
foi1
, uma otimização de nível de montagem diferente estava disponível, e novamente o desenvolvedor teve que torná-lo explícito, porque o compilador não reconhecê-lo. (Mais recentemente, compiladores fazem, portanto, essas sintaxes são praticamente desnecessárias atualmente)fonte
Parece que ninguém mencionou o problema de Dinah com
sizeof
:Você só pode adicionar um número inteiro a um ponteiro, não pode adicionar dois ponteiros juntos. Dessa forma, ao adicionar um ponteiro a um número inteiro, ou um número inteiro a um ponteiro, o compilador sempre sabe qual bit tem um tamanho que precisa ser levado em consideração.
fonte
Para responder a pergunta literalmente. Nem sempre é verdade que
x == x
impressões
fonte
cout << (a[5] == a[5] ? "true" : "false") << endl;
éfalse
.x == x
nem sempre é verdade). Eu acho que essa era a intenção dele. Então ele é tecnicamente correto (e possivelmente, como se costuma dizer, o melhor tipo de correto!).NAN
in<math.h>
, que é melhor que0.0/0.0
, porque0.0/0.0
é UB quando__STDC_IEC_559__
não está definido (a maioria das implementações não define__STDC_IEC_559__
, mas na maioria das implementações0.0/0.0
ainda funcionará)Acabei de descobrir que essa sintaxe feia pode ser "útil" ou pelo menos muito divertida de se brincar quando você deseja lidar com uma matriz de índices que se referem a posições na mesma matriz. Ele pode substituir colchetes aninhados e tornar o código mais legível!
Claro, tenho certeza de que não há nenhum caso de uso para isso no código real, mas achei interessante de qualquer maneira :)
fonte
i[a][a][a]
que pensa que eu sou um ponteiro para uma matriz ou uma matriz de um ponteiro para uma matriz ou uma matriz ... ea
é um índice. Quando você vêa[a[a[i]]]
, você acha que a é um ponteiro para uma matriz ou matriz ei
é um índice.Boa pergunta / respostas.
Só quero salientar que ponteiros e matrizes C não são os mesmos , embora neste caso a diferença não seja essencial.
Considere as seguintes declarações:
Em
a.out
, o símboloa
está em um endereço que é o início da matriz e o símbolop
em um endereço em que um ponteiro é armazenado, e o valor do ponteiro nesse local de memória é o início da matriz.fonte
int a[10]
foi um ponteiro chamado 'a', que apontava para armazenamento suficiente para 10 números inteiros, em outro lugar. Assim, a + i e j + i tinham a mesma forma: adicione o conteúdo de alguns locais de memória. Na verdade, eu acho que o BCPL não tinha tipo, então eles eram idênticos. E o tamanho do tipo de escala não se aplicava, já que o BCPL era puramente orientado a palavras (também em máquinas endereçadas por palavras).int*p = a;
comint b = 5;
No último, "b" e "5" são números inteiros, mas "b" é uma variável, enquanto "5" é um valor fixo. Da mesma forma, "p" e "a" são os endereços de um caractere, mas "a" é um valor fixo.Para ponteiros em C, temos
e também
Por isso, é verdade que
a[5] == 5[a].
fonte
Não é uma resposta, mas apenas um pouco de reflexão. Se a classe estiver sobrecarregando o operador de índice / índice, a expressão
0[x]
não funcionará:Como não temos acesso à classe int , isso não pode ser feito:
fonte
class Sub { public: int operator[](size_t nIndex) const { return 0; } friend int operator[](size_t nIndex, const Sub& This) { return 0; } };
operator[]
deve ser uma função membro não estática com exatamente um parâmetro." Eu estava familiarizado com essa restriçãooperator=
, não achei que fosse aplicável[]
.[]
operador, nunca mais seria equivalente ... sea[b]
for igual a*(a + b)
e você alterar isso, terá que sobrecarregar tambémint::operator[](const Sub&);
eint
não é uma classe ...Tem uma explicação muito boa em A TUTORIAL ON POINTERS AND ARRAYS IN C de Ted Jensen.
Ted Jensen explicou como:
fonte
Sei que a pergunta foi respondida, mas não pude resistir a compartilhar esta explicação.
Lembro-me de Principles of Compiler design, vamos assumir que
a
é umaint
matriz e o tamanhoint
é de 2 bytes, e o endereço base paraa
é 1000.Como
a[5]
vai funcionar ->Assim,
Da mesma forma, quando o código c for dividido em código de três endereços,
5[a]
ele se tornará ->Então, basicamente, ambas as instruções estão apontando para o mesmo local na memória e, portanto
a[5] = 5[a]
,.Essa explicação também é a razão pela qual índices negativos em matrizes funcionam em C.
ou seja, se eu acessar,
a[-5]
isso me daráEle retornará o objeto no local 990.
fonte
Em matrizes C ,
arr[3]
e3[arr]
são as mesmas, e os seus equivalentes são notações ponteiro*(arr + 3)
para*(3 + arr)
. Mas, ao contrário[arr]3
ou[3]arr
não é correto e irá resultar em erro de sintaxe, como(arr + 3)*
e(3 + arr)*
não são expressões válidas. O motivo é que o operador de desreferência deve ser colocado antes do endereço gerado pela expressão, não depois do endereço.fonte
no compilador c
Existem diferentes maneiras de se referir a um elemento em uma matriz! (NÃO É ESTRANHO)
fonte
Um pouco de história agora. Entre outras línguas, o BCPL teve uma influência bastante importante no desenvolvimento inicial de C. Se você declarou uma matriz no BCPL com algo como:
que realmente alocou 11 palavras de memória, não 10. Normalmente, V foi o primeiro e continha o endereço da palavra imediatamente seguinte. Assim, diferentemente de C, nomear V foi para esse local e pegou o endereço do elemento zeroeth da matriz. Portanto, a indireção da matriz no BCPL, expressa como
realmente precisava fazer
J = !(V + 5)
(usando a sintaxe BCPL), pois era necessário buscar V para obter o endereço base da matriz. AssimV!5
e5!V
eram sinônimos. Como uma observação anedótica, o WAFL (Warwick Functional Language) foi escrito em BCPL, e o melhor de minha memória tendeu a usar a última sintaxe, e não a primeira, para acessar os nós usados como armazenamento de dados. Concedido que isso é algo entre 35 e 40 anos atrás, então minha memória está um pouco enferrujada. :)A inovação de dispensar a palavra extra de armazenamento e fazer com que o compilador insira o endereço base da matriz quando foi nomeado veio mais tarde. De acordo com o artigo de história da C, isso aconteceu aproximadamente na época em que as estruturas foram adicionadas a C.
Observe que
!
no BCPL havia um operador de prefixo unário e um operador de infixo binário, nos dois casos fazendo indiretamente. apenas que a forma binária incluía uma adição dos dois operandos antes de fazer a indireção. Dada a natureza orientada para a palavra da BCPL (e B), isso realmente fez muito sentido. A restrição de "ponteiro e número inteiro" foi necessária em C quando ganhou tipos de dados esizeof
se tornou uma coisa.fonte
Bem, esse é um recurso que só é possível por causa do suporte ao idioma.
O compilador interpreta
a[i]
como*(a+i)
e a expressão é5[a]
avaliada como*(5+a)
. Como a adição é comutativa, verifica-se que ambos são iguais. Portanto, a expressão é avaliada comotrue
.fonte
Em C
Ponteiro é uma "variável"
nome da matriz é um "mnemônico" ou "sinônimo"
p++;
é válido, masa++
é inválidoa[2]
é igual a 2 [a] porque a operação interna de ambos é"Aritmética do ponteiro" calculada internamente como
*(a+3)
é igual a*(3+a)
fonte
tipos de ponteiro
1) ponteiro para dados
2) ponteiro const para dados
3) ponteiro const para dados const
e as matrizes são do tipo (2) da nossa lista
Quando você define uma matriz de cada vez, um endereço é inicializado nesse ponteiro
Como sabemos que não podemos alterar ou modificar o valor const no nosso programa, pois gera um erro ao compilar Tempo
A principal diferença que encontrei é ...
Podemos reinicializar o ponteiro por um endereço, mas não o mesmo caso com uma matriz.
======
e voltando à sua pergunta ...
a[5]
não é nada, mas*(a + 5)
você pode entender facilmente
a
- contendo o endereço (as pessoas chamam de endereço base) como um tipo de ponteiro (2) em nossa lista[]
- que o operador pode ser substituível com ponteiro*
.então finalmente ...
fonte