c arrays size language-lawyer pointer-arithmetic

¿Cómo determina este fragmento de código el tamaño de la matriz sin usar sizeof()?



arrays language-lawyer (3)

Pasando por algunas preguntas de la entrevista con C, encontré una pregunta que dice "¿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 era de esperar, devuelve 5.

edición: la gente señaló this respuesta, pero la sintaxis difiere un poco, es decir, el método de indexación

size = (&arr)[1] - arr;

así que creo que ambas preguntas son válidas y tienen un enfoque ligeramente diferente al problema. ¡Gracias a todos por la inmensa ayuda y la completa explicación!


Cuando agrega 1 a un puntero, el resultado es la ubicación del siguiente objeto en una secuencia de objetos del tipo apuntado (es decir, una matriz). Si p apunta a un objeto int , entonces p + 1 apuntará al siguiente int en una secuencia. Si p apunta a una matriz de 5 elementos de int (en este caso, la expresión &a ), p + 1 apuntará a la siguiente matriz de 5 elementos de int en una secuencia.

Restar dos punteros (siempre que ambos apunten al mismo objeto de matriz, o uno apunte uno más allá del último elemento de la matriz) produce el número de objetos (elementos de matriz) entre esos dos punteros.

La expresión &a produce la dirección de a , y tiene el tipo int (*)[5] (puntero a la matriz de 5 elementos de int ). La expresión &a + 1 produce la dirección de la siguiente matriz de 5 elementos de int sigue a , y también tiene el tipo int (*)[5] . La expresión *(&a + 1) anula el resultado de &a + 1 , de modo que produce la dirección del primer int sigue al último elemento de a , y tiene el tipo int [5] , que en este contexto "decae" a un expresión de tipo int * .

De manera similar, la expresión a "decae" en un puntero al primer elemento de la matriz y tiene el tipo int * .

Una imagen puede ayudar:

int [5] int (*)[5] int int * +---+ +---+ | | <- &a | | <- a | - | +---+ | | | | <- a + 1 | - | +---+ | | | | | - | +---+ | | | | | - | +---+ | | | | +---+ +---+ | | <- &a + 1 | | <- *(&a + 1) | - | +---+ | | | | | - | +---+ | | | | | - | +---+ | | | | | - | +---+ | | | | +---+ +---+

Estas son dos vistas del mismo almacenamiento: a la izquierda, lo vemos como una secuencia de arreglos de 5 elementos de int , mientras que a la derecha, lo vemos como una secuencia de int . También muestro las diversas expresiones y sus tipos.

Tenga en cuenta que la expresión *(&a + 1) produce un comportamiento indefinido :

...
Si el resultado apunta a uno más allá del último elemento del objeto de la matriz, no se utilizará como el operando de un operador unario * que se evalúa.

Borrador en línea C 2011 , 6.5.6 / 9


Esta línea es de la mayor importancia:

size = *(&a + 1) - a;

Como puede ver, primero toma la dirección de a y le agrega una. Luego, hace referencia a ese puntero y resta el valor original de a.

La aritmética de punteros 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 5 int s después de a . Después de eso, este código anula el puntero resultante y resta a (un tipo de matriz que se ha desintegrado en un puntero) de eso, dando el número de elementos en la matriz.

Detalles sobre cómo funciona la aritmética de punteros:

Supongamos que tiene un puntero xyz que apunta a un tipo int y contiene el valor (int *)160 . Cuando restas cualquier número de xyz , C especifica que la cantidad real que se resta de xyz es ese número multiplicado por el tamaño del tipo al que apunta. Por ejemplo, si restó 5 de xyz , el valor de xyz resultante sería xyz - (sizeof(*xyz) * 5) si la aritmética de punteros no se aplicara.

Como a es una matriz de 5 tipos int , el valor resultante será 5. Sin embargo, esto no funcionará con un puntero, solo con una matriz. Si lo intentas 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:

a + 0 | [a[0]] | &a points to this a + 1 | [a[1]] a + 2 | [a[2]] a + 3 | [a[3]] a + 4 | [a[4]] | end of array a + 5 | [a[5]] | &a+1 points to this; accessing past array when dereferenced

Esto significa que el código está restando a a &a[5] (o a+5 ), dando 5 .

Tenga en cuenta que este es un comportamiento indefinido, y no debe utilizarse bajo ninguna circunstancia. No espere que el comportamiento de esto sea consistente en todas las plataformas, y no lo use en programas de producción.


Hmm, sospecho que esto es algo que no hubiera funcionado en los primeros días de C. Aunque 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 de este tipo asumiendo que hay una matriz 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 este sentido, me refiero a la legalización del lenguaje, no funcionará en la práctica), dado el tipo de operaciones de tipo que se están realizando Por ejemplo, solo está "permitido" para restar dos punteros cuando apuntan a elementos en la misma matriz. *(&a+1) se sintetizó al acceder a otra matriz, aunque se trate de una matriz principal, por lo que no es realmente un puntero a la misma matriz que a . Además, aunque se le permite 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 desreferenciación ( * ) no está "permitida" en este puntero sintetizado, aunque ¡No tiene ningún comportamiento en este caso!

Sospecho que en los primeros días de C (sintaxis de K&R, ¿alguien?), Una matriz se descomponía en un puntero mucho más rápidamente, por lo que el *(&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 conozcan el tamaño de la matriz, y probablemente los estándares de C hayan seguido su ejemplo. Todo el código de función C solo toma los 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 de 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 que implementar, en cuyo caso, la desreferenciación del +1 sería fatal, e incluso si funcionara daría una respuesta incorrecta.

Otra posible debilidad puede ser que el compilador de C pueda alinear la matriz externa. Imagínese si se tratara de una matriz de 5 caracteres ( char arr[5] ), cuando el programa realiza &a+1 está invocando el comportamiento de "matriz de matriz". El compilador podría decidir que una matriz de matriz de 5 caracteres ( char arr[][5] ) se genere realmente como una matriz de matriz de 8 caracteres ( char arr[][8] ), de modo que la matriz externa se alinee bien. El código que estamos discutiendo ahora reportaría el tamaño del arreglo como 8, no 5. No estoy diciendo que un compilador en particular definitivamente haría esto, pero podría hacerlo.