Lista definitiva de razones comunes para las fallas de segmentación

NOTA: Tenemos muchas preguntas segfault, con la mayoría de las mismas respuestas, así que estoy tratando de colapsarlas en una pregunta canónica como la que tenemos para referencia indefinida .

Aunque tenemos una pregunta que cubre qué es una falla de segmentación , cubre el qué , pero no enumera muchas razones. La respuesta principal dice “hay muchas razones”, y solo enumera una, y la mayoría de las otras respuestas no enumeran ninguna razón.

Con todo, creo que necesitamos una wiki comunitaria bien organizada sobre este tema, que enumere todas las causas comunes (y algunas más) para obtener segfaults. El objective es ayudar en la depuración, como se menciona en el descargo de responsabilidad de la respuesta.

Sé lo que es una falla de segmentación, pero puede ser difícil de detectar en el código sin saber a qué se parecen. Aunque hay, sin duda, demasiados para enumerar exhaustivamente, ¿cuáles son las causas más comunes de las fallas de segmentación en C y C ++?

¡ADVERTENCIA!

Las siguientes son posibles razones para una falla de segmentación. Es prácticamente imposible enumerar todas las razones . El propósito de esta lista es ayudar a diagnosticar una segfault existente.

¡La relación entre las fallas de segmentación y el comportamiento indefinido no se puede enfatizar lo suficiente! Todas las situaciones siguientes que pueden crear una falla de segmentación son un comportamiento técnicamente indefinido. Eso significa que pueden hacer cualquier cosa , no solo segfault, como alguien dijo una vez en USENET, ” es legal para el comstackdor hacer que los demonios salgan volando de la nariz “. No cuente con un segfault si tiene un comportamiento indefinido. ¡Debería aprender qué comportamientos indefinidos existen en C y / o C ++, y evitar escribir código que los tenga!

Más información sobre Comportamiento Indefinido:

  • ¿Cuál es la forma estándar más sencilla para producir un Segfault en C?
  • Comportamiento indefinido, no especificado y definido por la implementación
  • ¿Qué tan indefinido es el comportamiento indefinido?

¿Qué es un Segfault?

En resumen, se produce una falla de segmentación cuando el código intenta acceder a la memoria a la que no tiene permiso de acceso . A cada progtwig se le da una pieza de memoria (RAM) con la que trabajar, y por razones de seguridad, solo se le permite acceder a la memoria en ese pedazo.

Para obtener una explicación técnica más completa acerca de qué es una falla de segmentación, consulte ¿Qué es un error de segmentación? .

Aquí están las razones más comunes para un error de falla de segmentación. Nuevamente, estos deben usarse para diagnosticar una segfault existente . Para aprender cómo evitarlos, aprenda los comportamientos indefinidos de su idioma.

Esta lista tampoco es un reemplazo para hacer su propio trabajo de depuración . (Consulte esa sección en la parte inferior de la respuesta). Estas son cosas que puede buscar, pero sus herramientas de depuración son la única manera confiable de concentrarse en el problema.


Accediendo a un puntero NULL o no inicializado

Si tiene un puntero que es NULL ( ptr=0 ) o que está completamente sin inicializar (todavía no está configurado para nada), intentar acceder o modificar usando ese puntero tiene un comportamiento indefinido.

 int* ptr = 0; *ptr += 5; 

Como una asignación fallida (como con malloc o new ) devolverá un puntero nulo, siempre debe verificar que su puntero no sea NULL antes de trabajar con él.

Tenga en cuenta también que incluso los valores de lectura (sin desreferencia) de los punteros no inicializados (y las variables en general) son un comportamiento indefinido.

A veces, este acceso de un puntero indefinido puede ser bastante sutil, como al tratar de interpretar dicho puntero como una cadena en una instrucción de impresión C.

 char* ptr; sprintf(id, "%s", ptr); 

Ver también:

  • Cómo detectar si la variable no iniciada / catch segfault en C
  • La concatenación de cadena e int resulta en falla seg. C

Accediendo a un puntero colgante

Si utiliza malloc o new para asignar memoria, y luego free o delete esa memoria a través del puntero, ese puntero ahora se considera un puntero colgante . Desreferenciarlo (así como simplemente leer su valor, es decir, no le asignó ningún valor nuevo, como NULL) es un comportamiento indefinido y puede dar como resultado un error de segmentación.

 Something* ptr = new Something(123, 456); delete ptr; std::cout << ptr->foo << std::endl; 

Ver también:

  • ¿Qué es una referencia colgante?
  • ¿Por qué mi puntero colgante no causa una falla de segmentación?

Desbordamiento de stack

[No, no es el sitio en el que se encuentra ahora, de lo que se llama así .] Oversimplified, la "stack" es como ese pico en el que coloca su pedido en algunos comensales. Este problema puede ocurrir cuando pones demasiadas órdenes en ese pico, por así decirlo. En la computadora, cualquier variable que no esté asignada dinámicamente y cualquier comando que aún deba ser procesado por la CPU, pasa a la stack.

Una de las causas de esto podría ser la recursión profunda o infinita, como cuando una función se llama a sí misma sin forma de detenerse. Debido a que la stack se ha desbordado, los documentos de pedido comienzan a "caerse" y ocupan otro espacio que no está destinado a ellos. Por lo tanto, podemos obtener un error de segmentación. Otra causa podría ser el bash de inicializar una matriz muy grande: es solo una orden única, pero que ya es lo suficientemente grande por sí misma.

 int stupidFunction(int n) { return stupidFunction(n); } 

Otra causa de un desbordamiento de stack sería tener demasiadas variables (no asignadas dinámicamente) a la vez.

 int stupidArray[600851475143]; 

Un caso de un desbordamiento de stack en la naturaleza provino de una simple omisión de una instrucción return en un condicional destinado a prevenir la recursión infinita en una función. La moraleja de esa historia, ¡ siempre asegúrate de que tus comprobaciones de errores funcionen!

Ver también:

  • Error de segmentación al crear matrices grandes en C
  • Seg Fault al inicializar array

Punteros salvajes

Crear un puntero a una ubicación aleatoria en la memoria es como jugar a la ruleta rusa con tu código: podrías perder fácilmente y crear un puntero a una ubicación a la que no tienes derecho de acceso.

 int n = 123; int* ptr = (&n + 0xDEADBEEF); //This is just stupid, people. 

Como regla general, no cree punteros a ubicaciones de memoria literales. Incluso si funcionan una vez, la próxima vez es posible que no. No puede predecir dónde estará la memoria de su progtwig en una ejecución determinada.

Ver también:

  • ¿Cuál es el significado de "puntero salvaje" en C?

Intentando leer más allá del final de una matriz

Una matriz es una región contigua de la memoria, donde cada elemento sucesivo se encuentra en la siguiente dirección en la memoria. Sin embargo, la mayoría de las matrices no tienen un sentido innato de qué tan grandes son o cuál es el último elemento. Por lo tanto, es fácil pasar el final de la matriz y nunca saberlo, especialmente si está utilizando aritmética de puntero.

Si lee más allá del final de la matriz, puede terminar yendo a la memoria que no está inicializada o pertenece a otra cosa. Este es un comportamiento técnicamente indefinido . Un segfault es solo uno de esos muchos comportamientos indefinidos potenciales. [Francamente, si obtienes un segfault aquí, tienes suerte. Otros son más difíciles de diagnosticar.]

 // like most UB, this code is a total crapshoot. int arr[3] {5, 151, 478}; int i = 0; while(arr[i] != 16) { std::cout << arr[i] << std::endl; i++; } 

O el que se usa con frecuencia con <= lugar de < (lee 1 byte en exceso):

 char arr[10]; for (int i = 0; i<=10; i++) { std::cout << arr[i] << std::endl; } 

O incluso un error tipográfico desafortunado que comstack bien (visto aquí ) y asigna solo 1 elemento inicializado con elementos dim lugar de dim .

 int* my_array = new int(dim); 

Además, debe tenerse en cuenta que ni siquiera se le permite crear (sin mencionar la desreferenciación) un puntero que señala fuera de la matriz (puede crear dicho puntero solo si apunta a un elemento dentro de la matriz, o uno más allá del final). De lo contrario, estás desencadenando un comportamiento indefinido.

Ver también:

  • ¡Tengo segfaults!

Olvidando un terminador NUL en una cadena C.

Las cadenas C son, en sí mismas, matrices con algunos comportamientos adicionales. Deben estar terminados en nulo, lo que significa que tienen un \0 al final, para ser utilizados de manera confiable como cadenas. Esto se hace automáticamente en algunos casos, y no en otros.

Si se olvida esto, algunas funciones que manejan cadenas C nunca saben cuándo detenerse, y usted puede tener los mismos problemas que con leer más allá del final de una matriz.

 char str[3] = {'f', 'o', 'o'}; int i = 0; while(str[i] != '\0') { std::cout << str[i] << std::endl; i++; } 

Con C-strings, realmente es impredecible si \0 hará alguna diferencia. Debe suponer que se evitará un comportamiento indefinido: mejor escriba char str[4] = {'f', 'o', 'o', '\0'};


Intentando modificar un literal de cadena

Si asigna un literal de cadena a un char *, no se puede modificar. Por ejemplo...

 char* foo = "Hello, world!" foo[7] = 'W'; 

... desencadena un comportamiento indefinido , y una falla de segmentación es un posible resultado.

Ver también:

  • ¿Por qué este código C de inversión de cadena provoca un error de segmentación?

Métodos de Asignación y Desasignación no coincidentes

Debe usar malloc y free together, new y delete together, y new[] y delete[] juntos. Si los mezclas, puedes obtener segfaults y otro comportamiento extraño.

Ver también:

  • Comportamiento de malloc con delete en C ++
  • Error de segmentación (núcleo volcado) cuando elimino el puntero

Errores en la cadena de herramientas.

Un error en el back-end de código de máquina de un comstackdor es bastante capaz de convertir un código válido en un ejecutable que falla por defecto. Un error en el enlazador definitivamente puede hacer esto también.

Particularmente aterrador porque esto no es invocado por UB con tu propio código.

Dicho esto, siempre debe asumir que el problema es usted hasta que se demuestre lo contrario.


Otras causas

Las posibles causas de las fallas de segmentación son tan numerosas como el número de comportamientos indefinidos, y hay demasiadas incluso para la lista de la documentación estándar.

Algunas causas menos comunes para verificar:

  • UD2 generado en algunas plataformas debido a otros UB
  • c ++ STL map :: operator [] hecho en una entrada que se borra

DEBUGGING

Las herramientas de depuración son fundamentales para diagnosticar las causas de una segfault. Compile su progtwig con el indicador de depuración ( -g ), y luego ejecútelo con su depurador para encontrar dónde es probable que ocurra el segfault.

Los comstackdores recientes admiten la construcción con -fsanitize=address , lo que normalmente da como resultado un progtwig que se ejecuta aproximadamente 2 veces más lento pero que puede detectar errores de dirección con mayor precisión. Sin embargo, otros errores (como la lectura de la memoria no inicializada o la filtración de recursos que no son de memoria, como los descriptores de archivos) no son compatibles con este método, y es imposible utilizar muchas herramientas de depuración y ASan al mismo tiempo.

Algunos depuradores de memoria

  • GDB | Mac, Linux
  • valgrind (memcheck) | Linux
  • Dr. Memory | Windows

Además, se recomienda utilizar herramientas de análisis estáticas para detectar comportamientos indefinidos, pero una vez más, son una herramienta simplemente para ayudarlo a encontrar un comportamiento indefinido, y no garantizan encontrar todas las instancias de comportamiento indefinido.

Sin embargo, si realmente tiene mala suerte, usar un depurador (o, más raramente, simplemente recomstackr con información de depuración) puede influir en el código y la memoria del progtwig lo suficiente como para que ya no ocurra el segfault, un fenómeno conocido como heisenbug .