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 + 1no 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
papunta a unintobjeto,p + 1apuntará al siguienteinten una secuencia. Sipapunta a una matriz de 5 elementos deint(en este caso, la expresión&a),p + 1apuntará a la siguiente matriz de 5 elementos deintuna 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
&aproduce la dirección deay tiene el tipoint (*)[5](puntero a la matriz de 5 elementos deint). La expresión&a + 1produce la dirección de la siguiente matriz de 5 elementos deintsiguientea, 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 queintsigue 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
ay 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&aes un puntero a la siguiente matriz de 5ints 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
xyzque apunta a uninttipo y contiene el valor(int *)160. Cuando resta cualquier número dexyz, C especifica que la cantidad real restadaxyzes ese número multiplicado por el tamaño del tipo al que apunta. Por ejemplo, si resta5dexyz, el valor dexyzresultado seríaxyz - (sizeof(*xyz) * 5)si la aritmética del puntero no se aplicara.Como
aes una matriz de5inttipos, 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
ade&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:
&aobtiene un puntero a un objeto de tipo int [5]+1obtiene el siguiente objeto suponiendo que hay una serie de esos*convierte efectivamente esa dirección en puntero de tipo a int-aresta 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+1invoca 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+1se 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].