c++ - servicio - segmentacion de mercado de consumo ejemplos
Lista definitiva de razones comunes para fallas de segmentación (1)
NOTA: Tenemos muchas preguntas segfault, en gran medida con las mismas respuestas, por lo que estoy tratando de colapsarlas en una pregunta canónica como la que tenemos para referencia indefinida .
Aunque tenemos una pregunta sobre 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.
En general, creo que necesitamos un wiki comunitario bien organizado sobre este tema, que enumere todas las causas comunes (y luego algunas) para obtener segfaults. El propósito 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 detectarlo en el código sin saber cómo se ven a menudo. Aunque hay, sin duda, demasiados para enumerarlos exhaustivamente, ¿cuáles son las causas más comunes de 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 un defecto existente.
¡La relación entre fallas de segmentación y comportamiento indefinido no se puede enfatizar lo suficiente! Todas las situaciones a continuación que pueden crear una falla de segmentación son comportamientos técnicamente indefinidos. Eso significa que pueden hacer cualquier cosa , no solo por defecto, como alguien dijo una vez en USENET, " es legal que el compilador haga que los demonios salgan volando de tu nariz ". No cuente con que ocurra una falla predeterminada cuando tenga un comportamiento indefinido. ¡Debe 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 de conformación estándar más simple 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 un error de segmentación cuando el código intenta acceder a la memoria a la que no tiene permiso de acceso . A cada programa se le asigna una pieza de memoria (RAM) para trabajar, y por razones de seguridad, solo se le permite acceder a la memoria en ese fragmento.
Para obtener una explicación técnica más exhaustiva sobre qué es un error de segmentación, consulte ¿Qué es un error de segmentación? .
Estas son las razones más comunes para un error de falla de segmentación. Una vez más, estos deberían usarse para diagnosticar una falla por defecto existente . Para aprender cómo evitarlos, aprenda los comportamientos indefinidos de su idioma.
Esta lista tampoco reemplaza a su propio trabajo de depuración . (Consulte esa sección al final de la respuesta). Estas son cosas que puede buscar, pero sus herramientas de depuración son la única forma confiable de concentrarse en el problema.
Acceder a un puntero NULL o no inicializado
Si tiene un puntero que es NULL (
ptr=0
) o que no está completamente inicializado (todavía no está configurado para nada), intentar acceder o modificarlo usando un puntero tiene un comportamiento indefinido.
int* ptr = 0;
*ptr += 5;
Dado que 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 leer valores (sin desreferenciar) de punteros no inicializados (y variables en general) es un comportamiento indefinido.
A veces, este acceso de un puntero indefinido puede ser bastante sutil, como al intentar interpretar dicho puntero como una cadena en una declaración de impresión C.
char* ptr;
sprintf(id, "%s", ptr);
Ver también:
- Cómo detectar si la variable no inicializada / captura segfault en C
- La concatenación de cadenas y resultados int en la falla seg C
Acceder a un puntero colgante
Si usa
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, dado que no le asignó algún valor nuevo, como NULL) es un comportamiento indefinido y puede provocar 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 pila
[No, no es el sitio en el que estás ahora, para qué lleva su nombre .] Simplificado en exceso, la "pila" es como esa espiga en la que pegas el 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 no haya sido procesado por la CPU, se almacena en la pila.
Una causa de esto podría ser una recursión profunda o infinita, como cuando una función se llama a sí misma sin forma de detenerse. Debido a que esa pila se ha desbordado, los documentos de pedido comienzan a "caerse" y ocupan otro espacio que no es para ellos. Por lo tanto, podemos obtener una falla de segmentación. Otra causa podría ser el intento de inicializar una matriz muy grande: es solo un orden, pero ya es lo suficientemente grande por sí mismo.
int stupidFunction(int n)
{
return stupidFunction(n);
}
Otra causa de un desbordamiento de la pila sería tener demasiadas variables (no asignadas dinámicamente) a la vez.
int stupidArray[600851475143];
Un caso de desbordamiento de pila en la naturaleza provino de una simple omisión de una declaración de
return
en un condicional destinado a evitar la recursión infinita en una función.
La moraleja de esa historia, ¡
siempre asegura que tus verificaciones de error funcionen!
Ver también:
- Error de segmentación al crear grandes matrices en C
- Seg Fault al inicializar la matriz
Punteros salvajes
Crear un puntero a una ubicación aleatoria en la memoria es como jugar a la ruleta rusa con su código: puede perder fácilmente y crear un puntero a una ubicación a la que no tiene derechos 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 trabajan una vez, la próxima vez podrían no hacerlo. No puede predecir dónde estará la memoria de su programa en una ejecución determinada.
Ver también:
Intentando leer más allá del final de una matriz
Una matriz es una región contigua de 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 cuán 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 se ha inicializado o que pertenece a otra cosa. Este es un comportamiento técnicamente indefinido . Un segfault es solo uno de esos muchos comportamientos indefinidos potenciales. [Francamente, si tienes un defecto 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 ve con frecuencia usando
for
con
<=
lugar de
<
(lee demasiado 1 byte):
char arr[10];
for (int i = 0; i<=10; i++)
{
std::cout << arr[i] << std::endl;
}
O incluso un error tipográfico desafortunado que compila bien (visto
here
) 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 apunte 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á desencadenando un comportamiento indefinido.
Ver también:
Olvidando un terminador NUL en una cadena C.
Las cadenas C son, en sí mismas, matrices con algunos comportamientos adicionales.
Deben tener terminación nula, 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 esto se olvida, algunas funciones que manejan cadenas C nunca saben cuándo detenerse, y puede tener los mismos problemas que con la lectura 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 lo hará para evitar un comportamiento indefinido: así que 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 está causando una falla de segmentación?
Métodos de asignación y desasignación no coincidentes
Debe usar
malloc
y
free
juntos,
new
y
delete
juntos, y
new[]
y
delete[]
juntos.
Si los mezcla, puede obtener segfaults y otros comportamientos extraños.
Ver también:
- Comportamiento de malloc con eliminar en C ++
- Falla de segmentación (núcleo volcado) cuando borro el puntero
Errores en la cadena de herramientas.
Un error en el backend de código de máquina de un compilador es bastante capaz de convertir el código válido en un ejecutable que falla. Un error en el enlazador definitivamente también puede hacer esto.
Particularmente aterrador en que esto no es invocado por UB por su propio código.
Dicho esto, siempre debes asumir que el problema eres tú 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 para que incluso la documentación estándar las enumere.
Algunas causas menos comunes para verificar:
- UD2 generado en algunas plataformas debido a otras UB
- c ++ STL map :: operator [] hecho en una entrada que se elimina
DEPURACIÓN
Las herramientas de depuración son fundamentales para diagnosticar las causas de una falla de seguridad.
Compile su programa con el indicador de depuración (
-g
), y luego ejecútelo con su depurador para encontrar dónde es probable que ocurra la falla predeterminada.
Los compiladores recientes admiten la construcción con
-fsanitize=address
, que generalmente da como resultado un programa que se ejecuta aproximadamente 2 veces más lento pero puede detectar errores de dirección con mayor precisión.
Sin embargo, este método no admite otros errores (como la lectura de la memoria no inicializada o la pérdida de recursos que no son de la memoria, como los descriptores de archivo), 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 | Ventanas
Además, se recomienda utilizar herramientas de análisis estático para detectar comportamientos indefinidos, pero nuevamente, son una herramienta simplemente para ayudarlo a encontrar comportamientos indefinidos, y no garantizan encontrar todas las ocurrencias de comportamientos indefinidos.
Sin embargo, si realmente tiene mala suerte, usar un depurador (o, más raramente, simplemente volver a compilar con información de depuración) puede influir en el código y la memoria del programa lo suficiente como para que la falla predeterminada ya no ocurra, un fenómeno conocido como heisenbug .