Como señala Joel en el podcast Stack Overflow # 34 , en C Programming Language (también conocido como: K & R), se menciona esta propiedad de los arrays en C:a[5] == 5[a]
Joel dice que se debe a la aritmética del puntero, pero aún no lo entiendo. ¿Por quéa[5] == 5[a]
?
c
arrays
pointers
pointer-arithmetic
Dinah
fuente
fuente
a[1]
como una serie de tokens, no cadenas: * ({ubicación entera de} a {operador} + {entero} 1) es lo mismo que * ({entero} 1 {operador} + {ubicación entera de} a) pero no es lo mismo que * ({ubicación entera de} un {operador} + {operador} +)char bar[]; int foo[];
yfoo[i][bar]
se usa como una expresión.a[b]
=*(a + b)
para cualquiera
yb
, pero fue la libre elección de los diseñadores de lenguaje para+
ser definidos conmutativos para todos los tipos. Nada podría impedirles prohibiri + p
mientras lo permitenp + i
.+
ser conmutativo, por lo que tal vez el verdadero problema sea elegir hacer que las operaciones de puntero se parezcan a la aritmética, en lugar de diseñar un operador de desplazamiento separado.Respuestas:
El estándar C define al
[]
operador de la siguiente manera:a[b] == *(a + b)
Por
a[5]
lo tanto evaluará a:y
5[a]
evaluará a:a
es un puntero al primer elemento de la matriz.a[5]
es el valor que está 5 elementos más lejosa
, que es el mismo*(a + 5)
, y de las matemáticas de la escuela primaria sabemos que son iguales (la suma es conmutativa ).fuente
a[5]
se compilará a algo así enmov eax, [ebx+20]
lugar de[ebx+5]
*(10 + (int *)13) != *((int *)10 + 13)
. En otras palabras, hay más cosas aquí que la aritmética de la escuela primaria. La conmutatividad se basa críticamente en el compilador que reconoce qué operando es un puntero (y a qué tamaño de objeto). Para decirlo de otra manera,(1 apple + 2 oranges) = (2 oranges + 1 apple)
, pero(1 apple + 2 oranges) != (1 orange + 2 apples)
.Porque el acceso a la matriz se define en términos de punteros.
a[i]
se define como media*(a + i)
, que es conmutativa.fuente
*(i + a)
, que se puede escribir comoi[a]
".*(a + i)
es conmutativo". Sin embargo,*(a + i) = *(i + a) = i[a]
porque la suma es conmutativa.Creo que las otras respuestas se están perdiendo algo.
Sí,
p[i]
es por definición equivalente a*(p+i)
, que (porque la suma es conmutativa) es equivalente a*(i+p)
, que (de nuevo, por definición del[]
operador) es equivalente ai[p]
.(Y en
array[i]
, el nombre de la matriz se convierte implícitamente en un puntero al primer elemento de la matriz).Pero la conmutatividad de la suma no es tan obvia en este caso.
Cuando ambos operandos son del mismo tipo, o incluso de diferentes tipos numéricos que se promueven a un tipo común, la conmutatividad tiene mucho sentido:
x + y == y + x
.Pero en este caso estamos hablando específicamente de la aritmética de punteros, donde un operando es un puntero y el otro es un número entero. (Entero + entero es una operación diferente, y puntero + puntero no tiene sentido).
La descripción del
+
operador del estándar C ( N1570 6.5.6) dice:Podría haber dicho con la misma facilidad:
en cuyo caso ambos
i + p
yi[p]
sería ilegal.En términos de C ++, realmente tenemos dos conjuntos de
+
operadores sobrecargados , que pueden describirse libremente como:y
de los cuales solo el primero es realmente necesario.
Entonces, ¿por qué es así?
C ++ heredó esta definición de C, que la obtuvo de B (la conmutatividad de la indexación de matrices se menciona explícitamente en la Referencia de usuarios de 1972 a B ), que la obtuvo de BCPL (manual fechado en 1967), que bien podría haberla obtenido incluso idiomas anteriores (CPL? Algol?).
Entonces, la idea de que la indexación de matrices se define en términos de suma, y esa suma, incluso de un puntero y un entero, es conmutativa, se remonta muchas décadas atrás, a los lenguajes ancestrales de C.
Esos lenguajes fueron mucho menos fuertemente tipados que el C moderno. En particular, a menudo se ignoraba la distinción entre punteros y enteros. (Los primeros programadores de C a veces usaban punteros como enteros sin signo, antes de
unsigned
palabra clave se agregara al lenguaje). Por lo tanto, la idea de hacer que la suma no sea conmutativa porque los operandos son de diferentes tipos probablemente no se les habría ocurrido a los diseñadores de esos idiomas. Si un usuario quería agregar dos "cosas", ya sea que esas "cosas" sean números enteros, punteros u otra cosa, no dependía del idioma evitarlo.Y a lo largo de los años, cualquier cambio en esa regla habría roto el código existente (aunque el estándar ANSI C de 1989 podría haber sido una buena oportunidad).
Cambiar C y / o C ++ para requerir colocar el puntero a la izquierda y el entero a la derecha podría romper algún código existente, pero no habría pérdida de poder expresivo real.
Así que ahora tenemos
arr[3]
y3[arr]
significamos exactamente lo mismo, aunque la última forma nunca debería aparecer fuera del IOCCC .fuente
3[arr]
es un artefacto interesante, pero rara vez debería usarse. La respuesta aceptada a esta pregunta (< stackoverflow.com/q/1390365/356> ) que pregunté hace un tiempo ha cambiado la forma en que pensaba acerca de la sintaxis. Aunque a menudo técnicamente no hay una forma correcta o incorrecta de hacer estas cosas, este tipo de características lo hacen pensar de manera separada de los detalles de implementación. Hay un beneficio en esta forma diferente de pensar que se pierde en parte cuando te fijas en los detalles de implementación.ring16_t
que contenga 65535 produciría unring16_t
valor 1, independiente del tamaño deint
.Y por supuesto
La razón principal de esto fue que en los años 70, cuando se diseñó C, las computadoras no tenían mucha memoria (64KB era mucho), por lo que el compilador de C no hizo mucha verificación de sintaxis. Por lo tanto, "
X[Y]
" se tradujo ciegamente en "*(X+Y)
"Esto también explica las sintaxis "
+=
" y "++
". Todo en la forma "A = B + C
" tenía la misma forma compilada. Pero, si B era el mismo objeto que A, entonces estaba disponible una optimización de nivel de ensamblaje. Pero el compilador no era lo suficientemente brillante como para reconocerlo, por lo que el desarrollador tuvo que (A += C
). De manera similar, siC
fuera así1
, estaba disponible una optimización de nivel de ensamblaje diferente, y nuevamente el desarrollador tuvo que hacerlo explícito, porque el compilador no lo reconoció. (Los compiladores más recientes lo hacen, por lo que esas sintaxis son en gran medida innecesarias en estos días)fuente
Una cosa que nadie parece haber mencionado sobre el problema de Dinah con
sizeof
:Solo puede agregar un entero a un puntero, no puede agregar dos punteros juntos. De esa manera, al agregar un puntero a un entero, o un entero a un puntero, el compilador siempre sabe qué bit tiene un tamaño que debe tenerse en cuenta.
fuente
Para responder la pregunta literalmente. No siempre es cierto que
x == x
huellas dactilares
fuente
cout << (a[5] == a[5] ? "true" : "false") << endl;
esfalse
.x == x
no siempre es cierto). Creo que esa era su intención. Entonces él es técnicamente correcto (y posiblemente, como dicen, ¡el mejor tipo de correcto!).NAN
en la<math.h>
que es mejor que0.0/0.0
, debido a0.0/0.0
que es UB cuando__STDC_IEC_559__
no está definido (La mayoría de las implementaciones no definen__STDC_IEC_559__
, pero en la mayoría de las implementaciones0.0/0.0
seguirá funcionando)Me acabo de enterar de que esta sintaxis fea podría ser "útil", o al menos muy divertida de jugar cuando se quiere lidiar con una matriz de índices que se refieren a posiciones en la misma matriz. ¡Puede reemplazar los corchetes anidados y hacer que el código sea más legible!
Por supuesto, estoy bastante seguro de que no hay un caso de uso para eso en el código real, pero lo encontré interesante de todos modos :)
fuente
i[a][a][a]
que piensa que soy un puntero a una matriz o una matriz de un puntero a una matriz o una matriz ... ya
es un índice. Cuando veaa[a[a[i]]]
, cree que a es un puntero a una matriz o una matriz yi
es un índice.Buena pregunta / respuestas.
Solo quiero señalar que los punteros y las matrices C no son lo mismo , aunque en este caso la diferencia no es esencial.
Considere las siguientes declaraciones:
En
a.out
, el símboloa
está en una dirección que es el comienzo de la matriz, y el símbolop
está en una dirección donde se almacena un puntero, y el valor del puntero en esa ubicación de memoria es el comienzo de la matriz.fuente
int a[10]
fue un puntero llamado 'a', que apuntaba a suficiente almacenamiento para 10 enteros, en otro lugar. Por lo tanto, a + i y j + i tenían la misma forma: agregar el contenido de un par de ubicaciones de memoria. De hecho, creo que BCPL no tenía tipo, por lo que eran idénticos. Y la escala de tamaño de tipo no se aplicaba, ya que BCPL estaba puramente orientado a palabras (en máquinas con direcciones de palabras también).int*p = a;
aint b = 5;
En este último, "b" y "5" son ambos enteros, pero "B" es una variable, mientras que "5" es un valor fijo. Del mismo modo, "p" y "a" son direcciones de un carácter, pero "a" es un valor fijo.Para punteros en C, tenemos
y también
Por eso es cierto que
a[5] == 5[a].
fuente
No es una respuesta, sino solo algo de reflexión. Si la clase está sobrecargando el operador de índice / subíndice, la expresión
0[x]
no funcionará:Como no tenemos acceso a la clase int , esto no se puede hacer:
fuente
class Sub { public: int operator[](size_t nIndex) const { return 0; } friend int operator[](size_t nIndex, const Sub& This) { return 0; } };
operator[]
será una función miembro no estática con exactamente un parámetro". Estaba familiarizado con esa restricciónoperator=
, no pensé que se aplicara[]
.[]
operador, nunca volvería a ser equivalente ... sia[b]
es igual*(a + b)
y cambia esto, también tendrá que sobrecargarint::operator[](const Sub&);
yint
no es una clase ...Tiene muy buena explicación en UN TUTORIAL SOBRE PUNTEROS Y ARREGLOS EN C por Ted Jensen.
Ted Jensen lo explicó como:
fuente
Sé que la pregunta está respondida, pero no pude resistirme a compartir esta explicación.
Recuerdo los principios del diseño del compilador, supongamos que
a
es unaint
matriz y un tamaño deint
2 bytes, y la dirección base paraa
1000.Cómo
a[5]
funcionará ->Entonces,
Del mismo modo, cuando el código c se divide en código de 3 direcciones,
5[a]
se convertirá en ->Así que, básicamente, tanto las declaraciones apuntan a la misma ubicación en la memoria y, por tanto,
a[5] = 5[a]
.Esta explicación también es la razón por la cual los índices negativos en las matrices funcionan en C.
es decir, si
a[-5]
accedo me daráMe devolverá el objeto en la ubicación 990.
fuente
En C arrays ,
arr[3]
y3[arr]
son los mismos, y sus notaciones puntero equivalentes son*(arr + 3)
a*(3 + arr)
. Pero por el contrario[arr]3
, o[3]arr
no es correcto y resultará en el error de sintaxis, como(arr + 3)*
y(3 + arr)*
no son expresiones válidas. El motivo es que el operador de desreferencia debe colocarse antes de la dirección producida por la expresión, no después de la dirección.fuente
en el compilador de c
Hay diferentes maneras de referirse a un elemento en una matriz. (NO ES LO EXTRAÑO)
fuente
Un poco de historia ahora. Entre otros lenguajes, BCPL tuvo una influencia bastante importante en el desarrollo temprano de C. Si declaró una matriz en BCPL con algo como:
que en realidad asignó 11 palabras de memoria, no 10. Por lo general, V fue la primera y contenía la dirección de la siguiente palabra. Entonces, a diferencia de C, nombrar V fue a esa ubicación y recogió la dirección del elemento cero de la matriz. Por lo tanto, la indirección de matriz en BCPL, expresada como
realmente tenía que hacerlo
J = !(V + 5)
(usando la sintaxis BCPL) ya que era necesario buscar V para obtener la dirección base de la matriz. AsíV!5
y5!V
fueron sinónimos. Como observación anecdótica, WAFL (Warwick Functional Language) fue escrito en BCPL, y lo mejor de mi memoria solía usar la última sintaxis en lugar de la primera para acceder a los nodos utilizados como almacenamiento de datos. De acuerdo, esto es de hace 35 a 40 años, así que mi memoria está un poco oxidada. :)La innovación de prescindir de la palabra adicional de almacenamiento y hacer que el compilador inserte la dirección base de la matriz cuando se nombró vino más tarde. Según el documento de historia de C, esto sucedió aproximadamente cuando las estructuras se agregaron a C.
Tenga
!
en cuenta que en BCPL era un operador de prefijo unario y un operador de infijo binario, en ambos casos haciendo indirección. solo que la forma binaria incluía una adición de los dos operandos antes de hacer la indirección. Dada la naturaleza orientada a las palabras de BCPL (y B), esto realmente tenía mucho sentido. La restricción de "puntero e entero" se hizo necesaria en C cuando ganó tipos de datos, y sesizeof
convirtió en una cosa.fuente
Bueno, esta es una característica que solo es posible debido al soporte de idiomas.
El compilador interpreta
a[i]
como*(a+i)
y la expresión se5[a]
evalúa como*(5+a)
. Como la suma es conmutativa, resulta que ambos son iguales. Por lo tanto, la expresión se evalúa comotrue
.fuente
C ª
El puntero es una "variable"
El nombre de la matriz es un "mnemónico" o "sinónimo"
p++;
es válido peroa++
no es válidoa[2]
es igual a 2 [a] porque la operación interna en ambos es"Aritmética de puntero" calculada internamente como
*(a+3)
es igual*(3+a)
fuente
tipos de puntero
1) puntero a datos
2) puntero constante a los datos
3) puntero constante a datos constantes
y las matrices son del tipo (2) de nuestra lista
Cuando define una matriz a la vez , se inicializa una dirección en ese puntero
Como sabemos que no podemos cambiar o modificar el valor constante en nuestro programa porque genera un ERROR en la compilación hora
La principal diferencia que encontré es ...
Podemos reinicializar el puntero por una dirección, pero no el mismo caso con una matriz.
======
y volviendo a su pregunta ...
a[5]
no es más*(a + 5)
que puede entender fácilmente al
a
contener la dirección (la gente la llama como dirección base) como un (2) tipo de puntero en nuestra lista[]
: ese operador puede ser reemplazable con puntero*
.así que finalmente...
fuente