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:
Cuando se restan dos expresiones de puntero
P
yQ
, el tipo del resultado es un tipo integral con signo definido por la implementación; este tipo será el mismo que se define comostd::ptrdiff_t
en el<cstddef>
([support.types]).
(5.1) Si
P
yQ
evalúan como valores de puntero nulos, el resultado es 0.(5.2) De lo contrario, si
P
yQ
apuntan, respectivamente, a los elementosx[i]
yx[j]
del mismo objeto de matrizx
, la expresiónP - Q
tiene el valori−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 tipostd::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
yQ
, el tipo del resultado es un tipo integral con signo definido por la implementación; este tipo será el mismo que se define comostd::ptrdiff_t
en el<cstddef>
([support.types]).
- Si
P
yQ
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]
yx[j]
del mismo objeto de matrizx
, la expresiónP - Q
tiene el valori−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ónP
de tipo puntero, el resultado tiene el tipo deP
- Si
P
evalúa como un valor de puntero nulo yJ
evalúa como 0, el resultado es un valor de puntero nulo.- De lo contrario, si
P
apunta al elementox[i]
de un objeto de matrizx
con n elementos, 81 las expresionesP + J
yJ + P
(dondeJ
tiene el valorj
) apuntan al elemento (posiblemente hipotético)x[i+j]
si0≤i+j≤n
y la expresiónP - J
apunta al elemento (posiblemente hipotético)x[i−j]
si0≤i−j≤n
.- De lo contrario, el comportamiento es indefinido.