AFAIK, aunque no podemos crear una matriz de memoria estática de tamaño 0, pero podemos hacerlo con las dinámicas:
int a[0]{}; // Compile-time error
int* p = new int[0]; // Is well-defined
Como he leído, p
actúa como un elemento del pasado. Puedo imprimir la dirección que p
apunta.
if(p)
cout << p << endl;
Aunque estoy seguro de que no podemos desreferenciar ese puntero (pasado-último elemento) como no podemos hacerlo con los iteradores (pasado-último elemento), pero de lo que no estoy seguro es si se incrementa ese puntero
p
. ¿Es un comportamiento indefinido (UB) como con los iteradores?p++; // UB?
c++
pointers
undefined-behavior
dynamic-arrays
Itachi Uchiwa
fuente
fuente
std::vector
elemento con 0.begin()
ya es igual a,end()
por lo que no puede incrementar un iterador que apunta al principio.Respuestas:
Los punteros a elementos de matrices pueden apuntar a un elemento válido, o uno más allá del final. Si incrementa un puntero de una manera que va más de un final, el comportamiento es indefinido.
Para su matriz de tamaño 0,
p
ya está apuntando una más allá del final, por lo que no está permitido incrementarla.Ver C ++ 17 8.7 / 4 con respecto al
+
operador (++
tiene las mismas restricciones):fuente
x[i]
es el mismo quex[i + j]
es cuando ambosi
yj
tener el valor 0?x[i]
es el mismo elemento quex[i+j]
sij==0
.Supongo que ya tienes la respuesta; Si miras un poco más profundo: has dicho que incrementar un iterador externo es UB, por lo tanto: ¿Esta respuesta está en qué es un iterador?
El iterador es solo un objeto que tiene un puntero y el incremento de ese iterador realmente incrementa el puntero que tiene. Así, en muchos aspectos, un iterador se maneja en términos de un puntero.
Esto es de C ++ primer 5 edition de Lipmann.
Entonces es UB, no lo hagas.
fuente
En el sentido más estricto, este no es un Comportamiento indefinido, sino una implementación definida. Entonces, aunque no es aconsejable si planea admitir arquitecturas no convencionales, probablemente pueda hacerlo.
La cita estándar dada por interjay es buena, indicando UB, pero es solo el segundo mejor éxito en mi opinión, ya que trata con la aritmética puntero-puntero (curiosamente, uno es explícitamente UB, mientras que el otro no). Hay un párrafo que trata directamente sobre la operación en la pregunta:
Oh, espera un momento, ¿un tipo de objeto completamente definido? ¿Eso es todo? Quiero decir, en serio, escriba ? ¿Entonces no necesitas un objeto en absoluto?
Se necesita bastante lectura para encontrar realmente una pista de que algo allí podría no estar tan bien definido. Porque hasta ahora, se lee como si estuviera perfectamente permitido hacerlo, sin restricciones.
[basic.compound] 3
hace una declaración sobre qué tipo de puntero puede tener uno, y al no ser ninguno de los otros tres, el resultado de su operación claramente se ubicaría en 3.4: puntero no válido .Sin embargo, no dice que no se le permite tener un puntero no válido. Por el contrario, enumera algunas condiciones normales muy comunes (por ejemplo, la duración del final del almacenamiento) en las que los punteros se vuelven regularmente inválidos. Entonces, eso aparentemente es algo permitido. Y de hecho:
Estamos haciendo un "cualquier otro" allí, por lo que no se trata de Comportamiento indefinido, sino de implementación definida, por lo tanto generalmente permitida (a menos que la implementación explícitamente diga algo diferente).
Desafortunadamente, ese no es el final de la historia. Aunque el resultado neto ya no cambia a partir de ahora, se vuelve más confuso, cuanto más tiempo busque "puntero":
Lea como: OK, a quién le importa! Mientras un puntero apunte a algún lugar de la memoria , ¿estoy bien?
Lea como: OK, derivado de forma segura, lo que sea. No explica qué es esto, ni dice que realmente lo necesito. Seguramente derivado del diablo. Aparentemente, todavía puedo tener punteros no derivados de forma segura. Supongo que desreferenciarlos probablemente no sería una buena idea, pero es perfectamente permisible tenerlos. No dice lo contrario.
Oh, entonces puede que no importe, solo lo que pensé. Pero espera ... "puede que no"? Eso significa que también puede hacerlo . ¿Cómo puedo saber?
Espera, ¿entonces es posible que necesite llamar
declare_reachable()
a cada puntero? ¿Cómo puedo saber?Ahora, puede convertir a
intptr_t
, que está bien definido, dando una representación entera de un puntero derivado de forma segura. Para lo cual, por supuesto, al ser un número entero, es perfectamente legítimo y está bien definido incrementarlo a su antojo.Y sí, puede convertir la parte
intptr_t
posterior en un puntero, que también está bien definido. Solo que, al no ser el valor original, ya no se garantiza que tenga un puntero derivado de forma segura (obviamente). Aún así, en general, al pie de la letra, mientras se define la implementación, esto es algo 100% legítimo:La captura
Los punteros son enteros ordinarios, solo que los usas como punteros. ¡Oh, si eso fuera cierto!
Desafortunadamente, existen arquitecturas donde eso no es cierto en absoluto, y simplemente generar un puntero no válido (sin desreferenciarlo, solo tenerlo en un registro de puntero) causará una trampa.
Esa es la base de la "implementación definida". Eso, y el hecho de que incrementar un puntero cuando lo desee, por supuesto , podría causar un desbordamiento, que el estándar no quiere tratar. El final del espacio de direcciones de la aplicación puede no coincidir con la ubicación del desbordamiento, y usted ni siquiera sabe si existe un desbordamiento para los punteros en una arquitectura particular. En general, es un desastre de pesadilla, no en relación con los posibles beneficios.
Tratar con la condición de un objeto pasado por el otro lado, es fácil: la implementación simplemente debe asegurarse de que nunca se asigne ningún objeto para que el último byte en el espacio de direcciones esté ocupado. Así que está bien definido, ya que es útil y trivial para garantizar.
fuente
+
operador (desde el que++
fluye), lo que significa que señalar después de "uno después del final" no está definido.