ejemplos - lenguaje ensamblador c++
Ejecutable compilado en Debug: ¿Por qué no abortar correctamente en escribir inválido en NULL? (9)
Lo que no entiendo acerca de C / C ++ es:
Sí, todo el mundo lo usa para obtener ejecutables increíblemente rápidos, por lo que compilan con la optimización activada.
Pero para la compilación con la información de depuración activada, no nos importa la velocidad. Entonces, ¿por qué no incluir más información en ese modo de compilación, por ejemplo, detectar algunos segfaults antes de que sucedan? Efectivamente, inserte una assert(ptr != NULL)
antes de cada acceso a un puntero ptr
. ¿Por qué el compilador no puede hacer eso? De nuevo, eso debería estar desactivado por defecto, pero debería haber tal posibilidad, creo.
EDITAR: Algunas personas dijeron que la detección que sugerí no tiene sentido o no hace nada que el informe de segmentation fault
de segmentation fault
ya no haga. Pero lo que tengo en mente es un aborto más elegante e informativo, que imprime el nombre del archivo y el número de línea del código ofensivo, al igual que haría un assert()
.
¿Qué debería hacer el programa en ese caso? Si informa al usuario de un error, entonces eso es lo que hace segfault.
Si se supone que debe seguir y evitar el error, ¿cómo sabe qué hacer?
Sin mencionar que si de alguna manera supiera mágicamente cómo continuar correctamente, entonces tienes un error en tu versión de lanzamiento (las compilaciones de depuración están destinadas a ayudarte a identificar y corregir errores, no a ocultarlos).
En respuesta a la información adicional agregada a la pregunta (supongo que entendí mal su intención):
lo que tengo en mente es simplemente un aborto más elegante e informativo, que imprime el nombre del archivo y el número de línea del código ofensivo, al igual que haría un assert ().
Esto es algo que el compilador podría hacer: como dices, el compilador básicamente insertaría automáticamente un assert()
cualquier lugar al que se haya desreferenciado un puntero. Esto podría agregar bastante significativamente al tamaño de una compilación de depuración, pero probablemente aún sería aceptable para muchos (o la mayoría) de los propósitos. Creo que esta sería una opción razonable para un compilador.
No estoy seguro de qué dirían los fabricantes de compiladores ... Quizás publiquen una solicitud en el sitio Connect de Microsoft para el producto VC ++ y vean lo que dicen.
Creo que el póster original quiere que la aplicación se detenga en el depurador. Tendría acceso a todas las variables de la pila y a la pila para que tenga la oportunidad de descubrir por qué su programa se encuentra en este estado.
Si está desarrollando en C / C ++, un administrador de memoria de depuración puede ahorrarle mucho tiempo. Los desbordamientos de búfer, el acceso a la memoria eliminada, las pérdidas de memoria, etc. son bastante fáciles de encontrar y solucionar. Hay varios en el mercado o puede pasar 2 o 3 días para escribir el suyo y obtener el 90% de la funcionalidad necesaria. Si está escribiendo aplicaciones sin ellos, está haciendo su trabajo mucho más difícil de lo necesario.
Dado que tiene un archivo de símbolos para su ejecutable, es posible asignar la ubicación del bloqueo a un número de línea. El depurador hace esto por usted si lo está ejecutando en el depurador, como otros lo han mencionado. Visual C ++ incluso ofrece una depuración "justo a tiempo" donde, cuando el programa falla, puede adjuntar un depurador al proceso bloqueado para ver dónde está el problema.
Sin embargo, si desea tener esta funcionalidad en máquinas donde no está instalado Visual C ++, aún es posible hacerlo con alguna codificación. Puede configurar un manejador de excepciones usando SetUnhandledExceptionFilter
que se SetUnhandledExceptionFilter
cuando su programa falle. En el controlador, puede ver el registro de excepción y usar SymGetLineFromAddr64
para determinar qué línea fuente se estaba ejecutando. Hay muchas funciones en la biblioteca de "depuración de ayuda" que le permite extraer todo tipo de información. Vea los artículos en MSDN , y también los artículos en www.debuginfo.com .
Entonces, ¿está diciendo que antes de que el sistema arroje un error, debería arrojar un error para ... advertirle del error inminente?
¿Cuál sería el punto? Cuando recibo un segfault, sé que significa que recibí un segfault. No necesito un mensaje por separado que diga primero "ahora obtendrás un segfault".
¿Me estoy perdiendo el punto aquí? :pag
Editar: Veo lo que quiere decir en su edición, pero no es fácil de implementar. El problema es que no es el compilador o el lenguaje o el tiempo de ejecución el que decide qué sucedería si accede a un puntero malo. El lenguaje oficialmente no hace promesas o garantías sobre esto. En cambio, el sistema operativo desencadena un error, sin saber que se trata de un archivo ejecutable de depuración, sin saber qué número de línea desencadenó el problema o cualquier otra cosa. Lo único que dice este error es "trataste de acceder a la dirección X, y no puedo permitir eso. Muere". ¿Qué debería hacer el compilador con esto?
Entonces, ¿quién debería generar este útil mensaje de error? ¿Y cómo? El compilador podría hacerlo, claro, pero envolviendo cada acceso de puntero en el manejo de errores, para garantizar que si ocurre una violación de seg / acceso, lo atrapemos y activemos una afirmación. El problema es que esto sería ridículamente lento . No solo "demasiado lento para su lanzamiento", sino "demasiado lento para ser utilizable". También asume que el compilador tiene acceso a todos los códigos a los que llamas. ¿Qué sucede si llama a una función en una biblioteca de terceros? El puntero accede al interior que no se puede incluir en el código de manejo de errores, porque el compilador no genera código para esa biblioteca.
El sistema operativo podría hacerlo, suponiendo que estuviese dispuesto / capaz de cargar los archivos de símbolos relevantes, de alguna manera detectar si está ejecutando un ejecutable de depuración y así sucesivamente ... Solo así puede imprimir un número de línea. Habla sobre sobreingeniería. Este no es el trabajo del sistema operativo.
Y finalmente, ¿qué ganarías al hacer esto? ¿Por qué no simplemente iniciar su depurador? Se rompe automáticamente cuando sucede algo como esto, dándole un número de línea preciso y todo lo demás.
Podría hacerse, pero sería terriblemente complicado e involucraría tanto al compilador como al sistema operativo, y el beneficio sería extremadamente pequeño. Obtendrá una ventana emergente que le informará información que su depurador ya puede comunicarle. Y con esa información, entonces ... encenderías tu depurador de todos modos para descubrir qué salió mal.
Estoy de acuerdo con Michael Burr en que esto realmente no ayuda o hace nada.
Además, esto todavía no funcionaría para punteros colgantes que tienden a ser mucho más insidiosos y difíciles de rastrear que los punteros nulos.
Al menos con punteros nulos es lo suficientemente simple como para garantizar que sean válidos antes de eliminarlos.
Hay algunos problemas importantes con su sugerencia:
¿Qué condiciones desea que el compilador detecte? En Linux / x86, el acceso desalineado puede causar SIGBUS
y el desbordamiento de la pila puede causar SIGSEGV
, pero en ambos casos es técnicamente posible escribir la aplicación para detectar esas condiciones y fallar "con gracia". Se pueden detectar las comprobaciones de puntero NULL
, pero los errores más insidiosos se deben a un acceso de puntero no válido, en lugar de punteros NULL
.
Los lenguajes de programación C y C ++ proporcionan suficiente flexibilidad por lo que es imposible que un tiempo de ejecución determine con 100% de éxito si una dirección aleatoria dada es un puntero válido de un tipo arbitrario.
¿Qué le gustaría que hiciera el entorno de tiempo de ejecución cuando detecta esta situación? No puede corregir el comportamiento (a menos que creas en la magia). Solo puede continuar ejecutándose o saliendo. Pero espere un momento ... ¡eso es lo que sucede cuando se envía una señal! El programa sale, se genera un volcado de núcleo y los desarrolladores de la aplicación pueden usar ese volcado de núcleo para determinar el estado del programa cuando se colgó.
Lo que defiende realmente suena como si quisiera ejecutar su aplicación en un depurador ( gdb
) o mediante alguna forma de virtualización ( valgrind
). Esto ya es posible, pero no tiene sentido hacerlo de manera predeterminada, ya que no proporciona ningún beneficio a los no desarrolladores.
Actualizar para responder a los comentarios:
No hay ninguna razón para modificar el proceso de compilación para las versiones de depuración. Si necesita una versión de depuración "suave" de la aplicación, debe ejecutarla dentro de un depurador. Es muy fácil envolver su ejecutable en un script que hace esto para usted de forma transparente.
Ya existe el equivalente de una afirmación (prt! = NULL) como parte del SO. Es por eso que obtienes un segfault en vez de simplemente sobreescribir datos importantes importantes en la dirección 0 y luego realmente arruinar el sistema.
Buena idea, pero solo en un caso particular. Eso es antes de desreferenciar un puntero de funciton. La razón es que el depurador siempre entrará en acción, pero después de desreferenciar un puntero de función nulo, se dispara la pila. Por lo tanto, tiene problemas para encontrar el código ofensivo. Si la persona que llamó verificó antes de llamar, el depurador podría darle una pila completa.
Una verificación más genérica sería ver si el puntero apunta a la memoria que es ejecutable. NULL no lo es, pero muchos sistemas operativos también pueden usar funciones de la CPU para hacer que los segmentos de memoria específicos no sean ejecutables.
Hay una razón más por la que una assert(ptr != NULL)
simple assert(ptr != NULL)
no funcionará antes de que la desreferenciación de un puntero no funcione: no todos los punteros inválidos (incluso aquellos que comenzaron su vida como NULL) son de hecho iguales a 0.
Primero considere el caso donde tiene una estructura con varios miembros:
struct mystruct {
int first;
int second;
int third;
int fourth;
};
Si tiene un puntero ptr
a mystruct
e intenta acceder a ptr->second
, el compilador generará el código de los anuncios 4 (asumiendo enteros de 32 bits) para ptr
y ptr
a esa ubicación de memoria. Si ptr
es 0, la ubicación de la memoria real a la que se accedió será 4. Eso sigue siendo inválido, pero no se detectará con una simple aserción. (Se podría esperar razonablemente que el compilador verifique la dirección de ptr
antes de agregar 4, en cuyo caso la afirmación lo captaría).
En segundo lugar, considere el caso en el que tiene una matriz de struct mystruct
y pasa un elemento arbitrario a otra función. Si intenta acceder al segundo elemento de la matriz, comenzará a 16 bytes más allá del primer puntero. No hay forma de que se pueda esperar razonablemente que el compilador haga lo que quiera de manera confiable en todos los casos, sin capturar la aritmética legítima del puntero.
Lo que realmente desea hacer es usar el sistema operativo y el hardware para atrapar el acceso a la memoria no válido y no alineado, matar su aplicación y luego averiguar cómo obtener la información de depuración que necesita. La forma más fácil es simplemente ejecutar dentro de un depurador. Si está utilizando gcc en Linux, vea cómo generar un stacktace cuando falla mi aplicación C ++ . Supongo que hay formas similares de hacer lo mismo con otros compiladores.