vectores matrices matematicas listas lineal funcion elementos contar c arrays language-lawyer

matrices - Es `*((*(& array+1))-1)` seguro de usar para obtener el último elemento de una matriz automática?



vectores y matrices algebra lineal (7)

Supongamos que quiero obtener el último elemento de una matriz automática cuyo tamaño es desconocido. Sé que puedo utilizar el operador sizeof para obtener el tamaño de la matriz y obtener el último elemento en consecuencia.

Está usando *((*(&array + 1)) - 1) ¿es seguro?

Me gusta:

char array[SOME_SIZE] = { ... }; printf("Last element = %c", *((*(&array + 1)) - 1));

int array[SOME_SIZE] = { ... }; printf("Last element = %d", *((*(&array + 1)) - 1));

etc


Creo que es un comportamiento indefinido por las razones que Peter menciona en su respuesta .

Existe un gran debate sobre *(&array + 1) . Por un lado, la eliminación de referencias &array + 1 parece ser legal porque solo está cambiando el tipo de T (*)[] a T [] , pero por otro lado, sigue siendo un puntero a la memoria no inicializada, no utilizada y no asignada.

Mi respuesta se basa en lo siguiente:

C99 6.5.6.7 (Semántica de operadores aditivos)

Para los propósitos de estos operadores, un puntero a un objeto que no es un elemento de una matriz se comporta de la misma manera que un puntero al primer elemento de una matriz de longitud uno con el tipo de objeto como su tipo de elemento.

Dado que &array no es un puntero a un objeto que es un elemento de una matriz, entonces de acuerdo con esto, significa que el código es equivalente a:

char array_equiv[1][SOME_SIZE] = { ... }; /* ... */ printf("Last element = %c", *((*(&array_equiv[0] + 1)) - 1));

Es decir, &array es un puntero a una matriz de 10 caracteres, por lo que se comporta igual que un puntero al primer elemento de una matriz de longitud 1 donde cada elemento es una matriz de 10 caracteres.

Ahora, eso junto con la cláusula que sigue (ya mencionada en otras respuestas, este extracto exacto es robado flagrantemente de la respuesta de ameyCU ):

C99 Sección 6.5.6.8 -

[...]
si la expresión P apunta al último elemento de un objeto de matriz, la expresión (P) +1 puntos [...]
Si el resultado señala uno pasado el último elemento del objeto de la matriz, no se utilizará como el operando de un operador unario * que se evalúa.

Deja bastante claro que es UB: es equivalente a desreferenciar un puntero que señala un último elemento de array_equiv .

Sí, en el mundo real, probablemente funcione, ya que en realidad el código original en realidad no desreferencia una ubicación de memoria, es principalmente una conversión de tipo de T (*)[] a T [] , pero estoy bastante seguro de que un estricto punto de vista de cumplimiento de normas, es un comportamiento indefinido.


Imho que podría funcionar, pero probablemente no sea sabio. Debe revisar cuidadosamente su diseño de sw y pregúntese por qué quiere la última entrada de la matriz. ¿El contenido de la matriz es completamente desconocido para usted o es posible definir la estructura en términos de estructuras y uniones? Si ese es el caso, aléjese de las operaciones complejas del puntero en una matriz char por ejemplo y defina los datos correctamente en su código c, en estructuras y uniones donde sea posible.

Entonces, en lugar de:

printf("Last element = %c", *((*(&array + 1)) - 1));

Podría ser :

printf("Checksum = %c", myStruct.MyUnion.Checksum);

Esto aclara tu código. La última letra de tu matriz no significa nada para una persona que no esté familiarizada con lo que está en esta matriz. myStruct.myUnion.Checksum tiene sentido para cualquiera. Estudiar la estructura myStruct podría explicar toda la estructura de datos a cualquiera. Por favor, use algo así si se puede declarar de esa manera. Si se encuentra en una situación rara que no puede, estudie las respuestas anteriores, tienen sentido, creo


No creo que sea seguro.

Desde el estándar como @dasblinkenlight citado en su respuesta (ahora eliminado), también hay algo que me gustaría añadir:

C99 Sección 6.5.6.8 -

[...]
si la expresión P apunta al último elemento de un objeto de matriz, la expresión (P) +1 puntos [...]
Si el resultado señala uno pasado el último elemento del objeto de la matriz, no se utilizará como el operando de un operador unario * que se evalúa.

Entonces, como dice, no deberíamos hacer esto *(&array + 1) ya que irá más allá del último elemento de la matriz y por lo tanto * no debería usarse.

Como también es bien sabido, los punteros de desreferenciación que apuntan a una ubicación de memoria no autorizada conducen a un comportamiento indefinido .


No, no es.

&array es del tipo puntero a char[SOME_SIZE] (en el primer ejemplo dado). Esto significa &array + 1 puntos en la memoria inmediatamente después del final de la array . Desreferenciando eso (como en (*(&array+1)) da un comportamiento indefinido.

No es necesario analizar más. Una vez que hay alguna parte de una expresión que da un comportamiento indefinido, la expresión completa sí lo hace.


Probablemente sea seguro, pero hay algunas advertencias.

Supongamos que tenemos

T array[LEN];

Entonces &array es de tipo T(*)[LEN] .

A continuación, &array + 1 vuelve a ser de tipo T(*)[LEN] , apuntando justo después del final de la matriz original.

A continuación, *(&array + 1) es del tipo T[LEN] , que se puede convertir implícitamente a T* , apuntando justo después del final de la matriz original. (Así que NO desreferenciamos una ubicación de memoria no válida: el operador * no se evalúa).

A continuación, *(&array + 1) - 1 es de tipo T* , apuntando a la última ubicación de la matriz.

Finalmente, desreferenciamos esto (que es legítimo si la longitud de la matriz no es cero): *(*(&array + 1) - 1) da el último elemento de matriz, un valor de tipo T

Tenga en cuenta que el único momento en que realmente desreferenciamos un puntero es en este último paso.

Ahora, las posibles advertencias.

En primer lugar, *(&array + 1) aparece formalmente como un intento de desreferenciar un puntero que apunta a una ubicación de memoria no válida. Pero realmente no lo es. Esa es la naturaleza de los punteros de matriz: esta desreferencia formal solo cambia el tipo de puntero, en realidad no da como resultado un intento de recuperar el valor de la ubicación a la que se hace referencia. Es decir, la array es de tipo T[LEN] pero puede convertirse implícitamente en tipo &T , apuntando al primer elemento de la matriz; &array es un puntero para escribir T[LEN] , apuntando al comienzo de la matriz; *(&array+1) es nuevamente de tipo T[LEN] que puede convertirse implícitamente en tipo &T En ningún momento es un puntero realmente desreferenciado.

Segundo, &array + 1 puede de hecho ser una dirección inválida, pero realmente no lo es: mi manual de referencia C ++ 11 me dice explícitamente que "se espera que funcione un puntero al elemento uno más allá del final de una matriz" , y una declaración similar también se hace en K & R, así que creo que siempre ha sido un comportamiento estándar.

Finalmente, en el caso de una matriz de longitud cero, la expresión desreferencia la ubicación de memoria justo antes de la matriz, que puede ser no asignada / inválida. Pero este problema también surgiría si uno usara un enfoque más convencional utilizando sizeof() sin probar primero la longitud distinta de cero.

En resumen, no creo que haya nada indefinido o dependiente de la implementación sobre el comportamiento de esta expresión.


un)

Si tanto el operando del puntero como el resultado [de P + N] apuntan a elementos del mismo objeto de matriz, o uno más allá del último elemento del objeto de matriz, la evaluación no producirá un desbordamiento;
[...]
si la expresión P apunta a un elemento de un objeto de matriz o uno más allá del último elemento de un objeto de matriz, y la expresión Q apunta al último elemento del mismo objeto de matriz, la expresión ((Q) +1) - ( P) tiene el mismo valor que ((Q) - (P)) + 1 y as - ((P) - ((Q) +1)), y tiene el valor cero si la expresión P señala un último elemento del objeto de matriz, aunque la expresión (Q) +1 no apunta a un elemento del objeto de matriz.

Esto indica que los cálculos que usan elementos de matriz uno más allá del último elemento en realidad están completamente bien. Como algunas personas aquí han escrito que el uso de objetos inexistentes para los cálculos ya es ilegal, pensé que incluiría esa parte.

Entonces tenemos que cuidar esta parte:

Si el resultado señala uno pasado el último elemento del objeto de la matriz, no se utilizará como el operando de un operador unario * que se evalúa.

Hay una parte importante que las otras respuestas omitieron y es:

Si el operando del puntero apunta a un elemento de un objeto de matriz

Este no es el hecho. El operando del puntero al que hacemos referencia no es un puntero a un elemento de un objeto de matriz, es un puntero a un puntero. Entonces, esta cláusula completa es completamente irrelevante. Pero, también se afirma:

A los efectos de estos operadores [aditivos], un puntero a un objeto que no es un elemento de una matriz se comporta igual que un puntero al primer elemento de una matriz de longitud uno con el tipo de objeto como su tipo de elemento.

¿Qué significa esto?

Significa que nuestro puntero a un puntero es en realidad un puntero a una matriz, de longitud [1]. Y ahora podemos cerrar el ciclo, porque como dice el primer párrafo, podemos hacer cálculos con un pasado de la matriz, ¡así que podemos hacer cálculos con la matriz como si fuera una matriz de longitud [2]!

De una manera más gráfica:

ptr -> (ptr to int[10])[0] -> int[10] -> (ptr to int[10])[1]

Entonces, podemos hacer cálculos con (ptr to int [10]) [1], aunque técnicamente está fuera de la matriz de longitud [1].

segundo)

Los pasos que ocurren son:

array ptr de tipo int [SOME_SIZE] a la primera matriz de elementos

&array ptr a un ptr de tipo int [SOME_SIZE] al primer elemento de array

+ 1 ptr, uno más que el ptr de tipo int [SOME_SIZE]) a la primera matriz de elementos, a un ptr de tipo int

Este NO es aún un puntero a int [SOME_SIZE + 1], de acuerdo con C99 Sección 6.5.6.8. Esto aún NO es ptr + SOME_SIZE + 1

* Desreferenciamos el puntero al puntero. AHORA , después de la eliminación de referencias, tenemos un puntero de acuerdo con la Sección 6.5.6.8 de C99, que está más allá del elemento de la matriz y que no puede ser desreferenciada. Se permite que este puntero exista y se nos permite usar operadores en él, excepto el operador unario *. Pero aún no usamos ese en ese puntero.

-1 Ahora restamos uno del ptr de tipo int a uno después del último elemento de la matriz, dejando que ptr apunte al último elemento de la matriz.

* Desreferenciando un ptr a int al último elemento de la matriz, que es legal.

do)

Y por último pero no menos importante:

Si fuera ilegal, el desplazamiento de macro también sería ilegal, que se define como:
((size_t)(&((st *)0)->m))


  • array es una matriz de tipo int[SOME_SIZE]
  • &array es un puntero de tipo int(*)[SOME_SIZE]
  • &array + 1 es un puntero pasado del tipo int(*)[SOME_SIZE]
  • *(&array + 1) es un lvalue de tipo int[SOME_SIZE]

Las otras respuestas ya han citado las partes relevantes del estándar, pero creo que el último punto puede ayudar a despejar la confusión.

La confusión parece estar relacionada con la idea de que la desreferenciación &array + 1 produce un int* pasado que suena como debería ser razonable, incluso si el estándar lo prohíbe técnicamente.

Pero eso no es lo que sucede: desreferenciar está intentando producir un lvalue (esencialmente, una referencia) a un objeto inexistente de tipo int[SOME_SIZE] , algo que realmente no debería sonar para nada razonable.

Incluso si se tratara de un comportamiento definido, en lugar de utilizar un truco arcano, es mucho mejor hacer algo legible,

template< typename T, size_t N > T& last(T (&array)[N]) { return array[N-1]; } // ... int array[SOME_SIZE] = { ... }; printf("Last element = %d", last(array));