Al revisar algunas preguntas de la entrevista en C, encontré una pregunta que decía "¿Cómo encontrar el tamaño de una matriz en C sin usar el operador sizeof?", Con la siguiente solución. Funciona, pero no puedo entender por qué.
#include <stdio.h>
int main() {
int a[] = {100, 200, 300, 400, 500};
int size = 0;
size = *(&a + 1) - a;
printf("%d\n", size);
return 0;
}
Como se esperaba, devuelve 5.
editar: la gente señaló esta respuesta, pero la sintaxis difiere un poco, es decir, el método de indexación
size = (&arr)[1] - arr;
Por lo tanto, creo que ambas preguntas son válidas y tienen un enfoque ligeramente diferente del problema. ¡Gracias a todos por la inmensa ayuda y la explicación detallada!
c
arrays
size
language-lawyer
pointer-arithmetic
janojlic
fuente
fuente
&a + 1
no apunta a ningún objeto válido, por lo que no es válido.*((*(&array + 1)) - 1)
seguro usarlo para obtener el último elemento de una matriz automática? . tl; dr*(&a + 1)
invoca Undefined Behvaior(ptr)[x]
es lo mismo que*((ptr) + x)
.Respuestas:
Cuando agrega 1 a un puntero, el resultado es la ubicación del siguiente objeto en una secuencia de objetos del tipo señalado (es decir, una matriz). Si
p
apunta a unint
objeto,p + 1
apuntará al siguienteint
en una secuencia. Sip
apunta a una matriz de 5 elementos deint
(en este caso, la expresión&a
),p + 1
apuntará a la siguiente matriz de 5 elementos deint
una secuencia.Al restar dos punteros (siempre que ambos apunten al mismo objeto de matriz, o uno esté apuntando uno más allá del último elemento de la matriz) se obtiene el número de objetos (elementos de matriz) entre esos dos punteros.
La expresión
&a
produce la dirección dea
y tiene el tipoint (*)[5]
(puntero a la matriz de 5 elementos deint
). La expresión&a + 1
produce la dirección de la siguiente matriz de 5 elementos deint
siguientea
, y también tiene el tipoint (*)[5]
. La expresión*(&a + 1)
desreferencia el resultado de&a + 1
, de modo que produce la dirección del primero queint
sigue al último elemento dea
, y tiene tipoint [5]
, que en este contexto "decae" a una expresión de tipoint *
.Del mismo modo, la expresión
a
"decae" a un puntero al primer elemento de la matriz y tiene tipoint *
.Una imagen puede ayudar:
Estas son dos vistas del mismo almacenamiento: a la izquierda, lo vemos como una secuencia de matrices de 5 elementos
int
, mientras que a la derecha, lo vemos como una secuencia deint
. También muestro las diversas expresiones y sus tipos.Tenga en cuenta que la expresión
*(&a + 1)
da como resultado un comportamiento indefinido :C 2011 Borrador en línea , 6.5.6 / 9
fuente
size = (int*)(&a + 1) - a;
este código, ¿sería completamente válido? : oEsta línea es de suma importancia:
Como puede ver, primero toma la dirección de
a
y le agrega una. Luego, elimina la referencia de ese puntero y resta el valor original dea
él.La aritmética del puntero en C hace que esto devuelva el número de elementos en la matriz, o
5
. Agregar uno y&a
es un puntero a la siguiente matriz de 5int
s despuésa
. Después de eso, este código desreferencia el puntero resultante y restaa
(un tipo de matriz que se ha desintegrado a un puntero) de eso, dando el número de elementos en la matriz.Detalles sobre cómo funciona la aritmética del puntero:
Digamos que tiene un puntero
xyz
que apunta a unint
tipo y contiene el valor(int *)160
. Cuando resta cualquier número dexyz
, C especifica que la cantidad real restadaxyz
es ese número multiplicado por el tamaño del tipo al que apunta. Por ejemplo, si resta5
dexyz
, el valor dexyz
resultado seríaxyz - (sizeof(*xyz) * 5)
si la aritmética del puntero no se aplicara.Como
a
es una matriz de5
int
tipos, el valor resultante será 5. Sin embargo, esto no funcionará con un puntero, solo con una matriz. Si intenta esto con un puntero, el resultado siempre será1
.Aquí hay un pequeño ejemplo que muestra las direcciones y cómo esto no está definido. El lado izquierdo muestra las direcciones:
Esto significa que el código está restando
a
de&a[5]
(oa+5
), dando5
.Tenga en cuenta que este es un comportamiento indefinido y no debe usarse bajo ninguna circunstancia. No espere que el comportamiento de este sea consistente en todas las plataformas, y no lo use en programas de producción.
fuente
Hmm, sospecho que esto es algo que no habría funcionado en los primeros días de C. Sin embargo, es inteligente.
Tomando los pasos uno a la vez:
&a
obtiene un puntero a un objeto de tipo int [5]+1
obtiene el siguiente objeto suponiendo que hay una serie de esos*
convierte efectivamente esa dirección en puntero de tipo a int-a
resta los dos punteros int, devolviendo el recuento de instancias int entre ellos.No estoy seguro de que sea completamente legal (en esto me refiero a un abogado de idiomas legal, no funcionará en la práctica), dadas algunas de las operaciones de tipo que están sucediendo. Por ejemplo, solo está "permitido" restar dos punteros cuando apuntan a elementos en la misma matriz.
*(&a+1)
se sintetizó accediendo a otra matriz, aunque sea una matriz principal, por lo que en realidad no es un puntero en la misma matriz quea
. Además, aunque puede sintetizar un puntero más allá del último elemento de una matriz, y puede tratar cualquier objeto como una matriz de 1 elemento, la operación de desreferenciar (*
) no está "permitida" en este puntero sintetizado, aunque no tiene comportamiento en este caso!Sospecho que en los primeros días de C (sintaxis K&R, ¿alguien?), Una matriz se descompuso en un puntero mucho más rápido, por lo que
*(&a+1)
podría devolver la dirección del siguiente puntero de tipo int **. Las definiciones más rigurosas de C ++ moderno definitivamente permiten que el puntero al tipo de matriz exista y conozca el tamaño de la matriz, y probablemente los estándares de C han seguido su ejemplo. Todo el código de función C solo toma punteros como argumentos, por lo que la diferencia técnica visible es mínima. Pero solo estoy adivinando aquí.Este tipo de pregunta de legalidad detallada generalmente se aplica a un intérprete de C, o una herramienta de tipo pelusa, en lugar del código compilado. Un intérprete podría implementar una matriz 2D como una matriz de punteros a matrices, porque hay una característica de tiempo de ejecución menos para implementar, en cuyo caso desreferenciar el +1 sería fatal, e incluso si funcionara, daría la respuesta incorrecta.
Otra posible debilidad puede ser que el compilador de C pueda alinear la matriz externa. Imagine si se tratara de una matriz de 5 caracteres (
char arr[5]
), cuando el programa lo realiza&a+1
invoca el comportamiento de "matriz de matriz". El compilador podría decidir que una matriz de matriz de 5 caracteres (char arr[][5]
) se genera realmente como una matriz de matriz de 8 caracteres (char arr[][8]
), de modo que la matriz externa se alinea muy bien. El código que estamos discutiendo ahora informaría que el tamaño de la matriz es 8, no 5. No estoy diciendo que un compilador particular definitivamente haga esto, pero podría.fuente
sizeof(array)/sizeof(array[0])
da el número de elementos en una matriz.&a+1
se define. Como señala John Bollinger,*(&a+1)
no lo es, ya que intenta desreferenciar un objeto que no existe.char [][5]
aschar arr[][8]
. Una matriz es solo los objetos repetidos en ella; No hay relleno. Además, esto rompería el ejemplo (no normativo) 2 en C 2018 6.5.3.4 7, que nos dice que podemos calcular el número de elementos en una matriz consizeof array / sizeof array[0]
.