c++ pointers language-lawyer pointer-arithmetic

c++ - Aritmética de punteros con dos buffers diferentes.



pointers language-lawyer (4)

Considere el siguiente código:

int* p1 = new int[100]; int* p2 = new int[100]; const ptrdiff_t ptrDiff = p1 - p2; int* p1_42 = &(p1[42]); int* p2_42 = p1_42 + ptrDiff;

Ahora, ¿el Estándar garantiza que p2_42 apunta a p2[42] ? Si no, ¿es siempre cierto en Windows, Linux o en el montón de ensamblaje web?


El Estándar permite implementaciones en plataformas donde la memoria se divide en regiones discretas que no se pueden alcanzar entre sí mediante la aritmética de punteros. Como ejemplo simple, algunas plataformas usan direcciones de 24 bits que consisten en un número de banco de 8 bits y una dirección de 16 bits dentro de un banco. Agregar uno a una dirección que identifique el último byte de un banco producirá un puntero al primer byte de ese mismo banco, en lugar del primer byte del siguiente banco. Este enfoque permite que la aritmética de direcciones y las compensaciones se calculen utilizando matemáticas de 16 bits en lugar de matemáticas de 24 bits, pero no requiere que ningún objeto abarque un límite bancario. Un diseño de este tipo impondría una complejidad adicional en malloc , y probablemente daría lugar a una mayor fragmentación de la memoria de lo que ocurriría, pero el código de usuario generalmente no tendría que preocuparse por la partición de la memoria en bancos.

Muchas plataformas no tienen tales restricciones de arquitectura, y algunos compiladores que están diseñados para la programación de bajo nivel en dichas plataformas permitirán que la aritmética de direcciones se realice entre punteros arbitrarios. El Estándar señala que una forma común de tratar el comportamiento indefinido es "comportarse durante la traducción o la ejecución del programa de una manera documentada característica del entorno", y el soporte para la aritmética de punteros generalizada en entornos que lo admitan encajaría muy bien en esa categoría. Desafortunadamente, el Estándar no proporciona ningún medio para distinguir las implementaciones que se comportan de una manera tan útil y aquellas que no lo hacen.


La tercera línea es un comportamiento indefinido, por lo que el estándar permite cualquier cosa después de eso.

Solo es legal restar dos punteros que apuntan a (o después) de la misma matriz.

Windows o Linux no son realmente relevantes; los compiladores y especialmente sus optimizadores son lo que rompe su programa. Por ejemplo, un optimizador puede reconocer que p1 y p2 apuntan al comienzo de un int[100] por lo que p1-p2 tiene que ser 0.


Para agregar la cotización estándar:

expr.add#5

Cuando se restan dos expresiones de puntero P y Q , el tipo del resultado es un tipo integral con signo definido por la implementación; este tipo será el mismo que se define como std::ptrdiff_t en el <cstddef> ([support.types]).

  • (5.1) Si P y Q evalúan como valores de puntero nulos, el resultado es 0.

  • (5.2) De lo contrario, si P y Q apuntan, respectivamente, a los elementos x[i] y x[j] del mismo objeto de matriz x , la expresión P - Q tiene el valor i−j .

  • (5.3) De lo contrario, el comportamiento es indefinido. [ Nota: Si el valor i−j no está en el rango de valores representables de tipo std::ptrdiff_t , el comportamiento no está definido. - nota final]

(5.1) no se aplica ya que los punteros no son nullptrs. (5.2) no se aplica porque los punteros no están en la misma matriz. Entonces, nos quedamos con (5.3) - UB.


const ptrdiff_t ptrDiff = p1 - p2;

Este es un comportamiento indefinido. La resta entre dos punteros está bien definida solo si apuntan a elementos en la misma matriz. ( [expr.add] ¶5.3 ).

Cuando se restan dos expresiones de puntero P y Q , el tipo del resultado es un tipo integral con signo definido por la implementación; este tipo será el mismo que se define como std::ptrdiff_t en el <cstddef> ([support.types]).

  • Si P y Q evalúan como valores de puntero nulo, el resultado es 0.
  • De lo contrario, si P y Q apuntan, respectivamente, a los elementos x[i] y x[j] del mismo objeto de matriz x , la expresión P - Q tiene el valor i−j .
  • De lo contrario, el comportamiento es indefinido.

E incluso si hubiera alguna forma hipotética de obtener este valor de forma legal, incluso esa suma es ilegal, ya que incluso la suma de un puntero + entero está restringida para permanecer dentro de los límites de la matriz ( [expr.add] ¶4.2 )

Cuando una expresión J que tiene un tipo integral se agrega o se resta de una expresión P de tipo puntero, el resultado tiene el tipo de P

  • Si P evalúa como un valor de puntero nulo y J evalúa como 0, el resultado es un valor de puntero nulo.
  • De lo contrario, si P apunta al elemento x[i] de un objeto de matriz x con n elementos, 81 las expresiones P + J y J + P (donde J tiene el valor j ) apuntan al elemento (posiblemente hipotético) x[i+j] si 0≤i+j≤n y la expresión P - J apunta al elemento (posiblemente hipotético) x[i−j] si 0≤i−j≤n .
  • De lo contrario, el comportamiento es indefinido.