c++ - que - punteros en c
¿Realizar aritmética en un puntero nulo es un comportamiento indefinido? (3)
La aritmética de punteros en un puntero que no apunta a una matriz es un comportamiento indefinido.
Además, la desreferenciación de un puntero NULO es un comportamiento indefinido.
char *c = NULL;
c--;
es un comportamiento definido no definido porque c
no apunta a una matriz.
Estándar C ++ 11 5.7.5:
Cuando una expresión que tiene tipo integral se agrega o se resta de un puntero, el resultado tiene el tipo del operando 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 con respecto al elemento original de manera que la diferencia de los subíndices de los elementos de la matriz resultante y original sea igual a la expresión integral. En otras palabras, si la expresión P apunta al elemento i-th de un objeto de matriz, las expresiones (P) + N (equivalentemente, N + (P)) y (P) -N (donde N tiene el valor n) punto a, respectivamente, los elementos i + n-th e i - n-th 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 uno más allá del último elemento del objeto de matriz, y si la expresión Q apunta uno 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 es indefinido.
Me parece que el siguiente programa calcula un puntero no válido, ya que NULL
no sirve para nada más que la asignación y la comparación para la igualdad:
#include <stdlib.h>
#include <stdio.h>
int main() {
char *c = NULL;
c--;
printf("c: %p/n", c);
return 0;
}
Sin embargo, parece que ninguna de las advertencias o instrumentaciones en GCC o Clang dirigidas a un comportamiento indefinido dicen que esto es, de hecho, UB. ¿Es esa aritmética realmente válida y estoy siendo demasiado pedante, o es una deficiencia en sus mecanismos de verificación que debo informar?
Probado
$ clang-3.3 -Weverything -g -O0 -fsanitize=undefined -fsanitize=null -fsanitize=address offsetnull.c -o offsetnull
$ ./offsetnull
c: 0xffffffffffffffff
$ gcc-4.8 -g -O0 -fsanitize=address offsetnull.c -o offsetnull
$ ./offsetnull
c: 0xffffffffffffffff
Parece estar bastante bien documentado que AddressSanitizer, tal como lo usan Clang y GCC, está más centrado en la desreferencia de malos punteros, por lo que es lo suficientemente justo. Pero los otros cheques tampoco lo atrapan: - /
Edición : parte de la razón por la que hice esta pregunta es que los indicadores -fsanitize
permiten verificaciones dinámicas de la definición del código generado. ¿Es esto algo que deberían haber atrapado?
La aritmética no solo está prohibida en un puntero nulo, sino que la falla de las implementaciones que atrapan las desreferencias y la aritmética en los punteros nulos degrada enormemente el beneficio de las trampas de puntero nulo.
El Estándar nunca define ninguna situación en la que agregar algo a un puntero nulo pueda generar un valor de puntero legítimo; Además, las situaciones en las que las implementaciones podrían definir cualquier comportamiento útil para tales acciones son raras y, en general, podrían manejarse mejor a través de los intrínsecos del compilador (*). Sin embargo, en muchas implementaciones, si la aritmética de puntero nulo no está atrapada, agregar un desplazamiento a un puntero nulo puede producir un puntero que, aunque no es válido, ya no es reconocible como un puntero nulo. Un intento de desreferenciar tal puntero no quedaría atrapado, pero podría desencadenar efectos arbitrarios.
Los cálculos del indicador de captura de la forma (nulo + desplazamiento) y (desplazamiento nulo) eliminarán este peligro. Tenga en cuenta que la protección no necesariamente requeriría intercepción (puntero-nulo), (nulo-puntero) o (nulo-nulo), mientras que los valores devueltos por las dos primeras expresiones probablemente no tendrán ninguna utilidad [si una implementación especificara ese nulo-nulo arrojaría cero, el código que apuntaba a esa implementación en particular a veces podría ser más eficiente que el código que tenía un caso especial null
] no generarían punteros no válidos. Además, el hecho de que (nulo + 0) y (nulo-0) produzcan punteros nulos en lugar de interceptación no pondría en peligro la seguridad y puede evitar la necesidad de tener punteros nulos en casos especiales de código de usuario, pero las ventajas serían menos convincentes ya que el compilador Tendría que agregar código extra para que eso suceda.
(*) Tal intrínseco en un compilador de 8086, por ejemplo, podría aceptar enteros de 16 bits sin signo "seg" y "ofs", y leer la palabra en la dirección seg: ofs sin una captura nula, incluso cuando la dirección fuera cero. . La dirección (0x0000: 0x0000) en el 8086 es un vector de interrupción al que algunos programas deben tener acceso, y mientras que la dirección (0xFFFF: 0x0010) accede a la misma ubicación física que (0x0000: 0x0000) en procesadores antiguos con solo 20 líneas de dirección, accede a la ubicación física 0x100000 en procesadores con 24 o más líneas de dirección). En algunos casos, una alternativa sería tener una designación especial para los punteros que se espera que apunten a cosas que no están reconocidas por el estándar C (cosas como los vectores de interrupción calificarían) y se abstengan de interceptarlas, o bien especificar que es volatile
Los punteros serán tratados de tal manera. He visto el primer comportamiento en al menos un compilador, pero no creas que he visto el segundo.
Sí, este es un comportamiento indefinido, y es algo que -fsanitize=undefined
debería haber capturado; ya está en mi lista de tareas pendientes para agregar un cheque para esto.
FWIW, las reglas de C y C ++ aquí son ligeramente diferentes: sumar 0
a un puntero nulo y restar un puntero nulo de otro tiene un comportamiento indefinido en C pero no en C ++. Toda otra aritmética sobre punteros nulos tiene un comportamiento indefinido en ambos idiomas.