Tome la dirección de un elemento de matriz pasado el final mediante un subíndice: ¿es legal según el estándar C++ o no?

Resuelto Zan Lynx asked hace 15 años • 14 respuestas

He visto afirmar varias veces que el estándar C++ no permite el siguiente código:

int array[5];
int *array_begin = &array[0];
int *array_end = &array[5];

¿Es &array[5]el código C++ legal en este contexto?

Me gustaría una respuesta con una referencia al Estándar, si es posible.

También sería interesante saber si cumple con el estándar C. Y si no es C++ estándar, ¿por qué se tomó la decisión de tratarlo de manera diferente a array + 5o &array[4] + 1?

Zan Lynx avatar Jun 13 '09 01:06 Zan Lynx
Aceptado

Sí, es legal. Del proyecto de norma C99 :

§6.5.2.1, párrafo 2:

Una expresión de sufijo seguida de una expresión entre corchetes []es una designación con subíndice de un elemento de un objeto de matriz. La definición del operador de subíndice [] es E1[E2]idéntica a (*((E1)+(E2))). Debido a las reglas de conversión que se aplican al +operador binario, si E1es un objeto de matriz (equivalente, un puntero al elemento inicial de un objeto de matriz) y E2es un número entero, E1[E2]designa el E2-ésimo elemento de E1(contando desde cero).

§6.5.3.2, párrafo 3 (el énfasis es mío):

El &operador unario da la dirección de su operando. Si el operando tiene tipo '' tipo '', el resultado tiene tipo ''puntero a tipo ''. Si el operando es el resultado de un *operador unario, ni ese operador ni el &operador se evalúan y el resultado es como si ambos se hubieran omitido, excepto que las restricciones de los operadores aún se aplican y el resultado no es un valor l. De manera similar, si el operando es el resultado de un operador, no se evalúa ni []el operador & ni el unario *implícito en y el resultado es como si se eliminara el operador y se cambiara a operador[]&[]+ . De lo contrario, el resultado es un puntero al objeto o función designado por su operando.

§6.5.6, párrafo 8:

Cuando una expresión que tiene tipo entero se suma o se resta de un puntero, el resultado tiene el tipo del operando del puntero. Si el operando puntero apunta a un elemento de un objeto de matriz, y la matriz es lo suficientemente grande, el resultado apunta a un elemento desplazado del elemento original de modo que la diferencia de los subíndices de los elementos de la matriz original y resultante sea igual a la expresión entera. En otras palabras, si la expresión Papunta al i-ésimo elemento de un objeto de matriz, las expresiones (P)+N(equivalentemente, N+(P)) y (P)-N(donde Ntiene el valor n) apuntan, respectivamente, a los i+n-ésimo y i−n-ésimo elemento del objeto de matriz, siempre que existir. Además, si la expresión Papunta al último elemento de un objeto de matriz, la expresión (P)+1apunta más allá del último elemento del objeto de matriz, y si la expresión Qapunta más allá del último elemento de un objeto de matriz, la expresión (Q)-1apunta al último elemento del objeto de matriz. Si tanto el operando puntero como el resultado 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; de lo contrario, el comportamiento no está definido. Si el resultado apunta uno más allá del último elemento del objeto de matriz, no se utilizará como operando de un *operador unario que se evalúa.

Tenga en cuenta que el estándar permite explícitamente que los punteros apunten a un elemento más allá del final de la matriz, siempre que no estén desreferenciados . Según 6.5.2.1 y 6.5.3.2, la expresión &array[5]es equivalente a &*(array + 5), que es equivalente a (array+5), que apunta uno más allá del final de la matriz. Esto no resulta en una desreferencia (según 6.5.3.2), por lo que es legal.

Adam Rosenfield avatar Jun 12 '2009 18:06 Adam Rosenfield

Su ejemplo es legal, pero sólo porque en realidad no está utilizando un puntero fuera de límites.

Tratemos primero con los punteros fuera de límites (porque así es como interpreté originalmente su pregunta, antes de notar que el ejemplo usa un puntero que pasa del final):

En general, ni siquiera se le permite crear un puntero fuera de límites. Un puntero debe apuntar a un elemento dentro de la matriz, o uno más allá del final . En ningún otro lugar.

Ni siquiera se permite que el puntero exista, lo que significa que obviamente tampoco se le permite eliminar la referencia a él.

Esto es lo que la norma tiene que decir sobre el tema:

5.7:5:

Cuando una expresión que tiene tipo integral se suma o se resta de un puntero, el resultado tiene el tipo del operando del puntero. Si el operando puntero apunta a un elemento de un objeto de matriz, y la matriz es lo suficientemente grande, el resultado apunta a un elemento desplazado del elemento original de modo que la diferencia de los subíndices de los elementos de la matriz original y resultante sea igual a la expresión integral. En otras palabras, si la expresión P apunta al i-ésimo elemento de un objeto de matriz, las expresiones (P)+N (equivalentemente, N+(P)) y (P)-N (donde N tiene el valor n) apuntan a, respectivamente, los elementos i+n-ésimo e i−n-ésimo del objeto de matriz, siempre que existan. Además, si la expresión P apunta al último elemento de un objeto de matriz, la expresión (P)+1 apunta más allá del último elemento del objeto de matriz, y si la expresión Q apunta más allá del último elemento de un objeto de matriz, la expresión (Q)-1 apunta al último elemento del objeto de matriz. Si tanto el operando puntero como el resultado 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; de lo contrario, el comportamiento no está definido .

(el énfasis es mío)

Por supuesto, esto es para operador+. Entonces, para estar seguro, esto es lo que dice el estándar sobre los subíndices de matrices:

5.2.1:1:

La expresión E1[E2]es idéntica (por definición) a*((E1)+(E2))

Por supuesto, hay una advertencia obvia: su ejemplo en realidad no muestra un puntero fuera de límites. utiliza un puntero "uno más allá del final", que es diferente. Se permite que el puntero exista (como dice lo anterior), pero el estándar, hasta donde puedo ver, no dice nada sobre desreferenciarlo. Lo más cercano que puedo encontrar es 3.9.2:3:

[Nota: por ejemplo, se consideraría que la dirección que pasa después del final de una matriz (5.7) apunta a un objeto no relacionado del tipo de elemento de la matriz que podría estar ubicado en esa dirección. —nota final]

Lo que me parece implica que sí, puedes eliminar la referencia legalmente, pero el resultado de leer o escribir en la ubicación no está especificado.

Gracias a ilproxyil por corregir lo último aquí, respondiendo la última parte de tu pregunta:

  • array + 5en realidad no elimina la referencia a nada, simplemente crea un puntero a uno más allá del final de array.
  • &array[4] + 1desreferencias array+4(lo cual es perfectamente seguro), toma la dirección de ese valor l y agrega uno a esa dirección, lo que da como resultado un puntero que pasa del final (pero ese puntero nunca se desreferencia.
  • &array[5]desreferencia matriz+5 (que hasta donde puedo ver es legal y da como resultado "un objeto no relacionado del tipo de elemento de la matriz", como se dijo anteriormente), y luego toma la dirección de ese elemento, que también parece bastante legal.

Por lo que no hacen exactamente lo mismo, aunque en este caso el resultado final es el mismo.

jalf avatar Jun 12 '2009 18:06 jalf

Es legal .

Según la documentación de gcc para C++ , &array[5]es legal. Tanto en C++ como en C, puede abordar con seguridad el elemento una vez pasado el final de una matriz; obtendrá un puntero válido. Entonces &array[5]como expresión es legal.

Sin embargo, todavía es un comportamiento indefinido intentar desreferenciar punteros a memoria no asignada, incluso si el puntero apunta a una dirección válida. Por lo tanto, intentar eliminar la referencia al puntero generado por esa expresión sigue siendo un comportamiento indefinido (es decir, ilegal) aunque el puntero en sí sea válido.

Sin embargo, en la práctica, imagino que normalmente no causaría un bloqueo.

Editar: Por cierto, así es generalmente como se implementa el iterador end() para contenedores STL (como un puntero a una vez pasado el final), por lo que es un testimonio bastante bueno de que la práctica es legal.

Editar: Oh, ahora veo que en realidad no estás preguntando si mantener un puntero a esa dirección es legal, sino si esa forma exacta de obtener el puntero es legal. Dejaré esto en manos de los demás que respondan.

Tyler McHenry avatar Jun 12 '2009 18:06 Tyler McHenry

Creo que esto es legal y depende de que se realice la conversión de 'lvalue a rvalue'. La última línea del problema principal 232 tiene lo siguiente:

Estuvimos de acuerdo en que el enfoque del estándar parece correcto: p = 0; *pag; no es inherentemente un error. Una conversión de valor a valor le daría un comportamiento indefinido

Aunque este es un ejemplo ligeramente diferente, lo que sí muestra es que '*' no da como resultado una conversión de valor l a valor r, por lo que, dado que la expresión es el operando inmediato de '&' que espera un valor l, entonces se define el comportamiento.

Richard Corden avatar Jun 12 '2009 18:06 Richard Corden