Lista definitiva de razones comunes para errores de segmentación
NOTA: Tenemos muchas preguntas de segmentación, con prácticamente las mismas respuestas, por lo que estoy tratando de agruparlas en una pregunta canónica como la que tenemos para referencia indefinida .
Aunque tenemos una pregunta que cubre qué es un error 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.
Considerándolo todo, creo que necesitamos una wiki comunitaria bien organizada sobre este tema, que enumere todas las causas comunes (y más) para obtener errores de segmentación. El propósito es ayudar en la depuración, como se menciona en el descargo de responsabilidad de la respuesta.
Sé lo que es un error de segmentación, pero puede ser difícil detectarlo en el código sin saber cómo se ven a menudo. Aunque, sin duda, hay demasiados para enumerarlos exhaustivamente, ¿ cuáles son las causas más comunes de errores de segmentación en C y C++?
¡ADVERTENCIA!
Las siguientes son posibles razones de un error de segmentación. Es prácticamente imposible enumerar todas las razones . El propósito de esta lista es ayudar a diagnosticar una falla de segmento existente.
¡ Nunca se insistirá lo suficiente en la relación entre los errores de segmentación y el comportamiento indefinido ! Todas las situaciones siguientes que pueden crear un error de segmentación son comportamientos técnicamente indefinidos. Eso significa que pueden hacer cualquier cosa , no solo segmentación; 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 de segmento cada vez que tenga un comportamiento indefinido. ¡Deberías aprender qué comportamientos indefinidos existen en C y/o C++ y evitar escribir código que los tenga!
Más información sobre el comportamiento indefinido:
- ¿Cuál es la forma más sencilla de ajustarse al estándar 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 una falla de segmento?
En resumen, una falla de segmentación se produce cuando el código intenta acceder a una memoria a la que no tiene permiso de acceso . A cada programa se le asigna una porción de memoria (RAM) para trabajar y, por razones de seguridad, solo se le permite acceder a la memoria en esa porción.
Para obtener una explicación técnica más detallada sobre qué es un error de segmentación , consulte ¿Qué es un error de segmentación? .
Estas son las razones más comunes de un error de segmentación. Nuevamente, estos deben usarse para diagnosticar una falla de segmento existente . Para aprender cómo evitarlos, aprenda los comportamientos indefinidos de su idioma .
Esta lista tampoco reemplaza la realización de su propio trabajo de depuración . (Consulte la 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.
Accediendo a un puntero NULL o no inicializado
Si tiene un puntero que es NULL ( ptr=0
) o que no está completamente inicializado (aún no está configurado en nada), intentar acceder o modificar usando ese 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 a un puntero indefinido puede ser bastante sutil, como al intentar interpretar dicho puntero como una cadena en una declaración de impresión en C.
char* ptr;
sprintf(id, "%s", ptr);
Ver también:
- Cómo detectar si la variable no está inicializada/captura de error de segmentación en C
- La concatenación de cadena e int da como resultado una falla de segmento C
Accediendo 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 . Eliminar la referencia a él (así como simplemente leer su valor, siempre que no le haya asignado ningú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 provoca un error de segmentación?
Desbordamiento de pila
[No, no el sitio en el que estás ahora, sino el nombre que le dio.] Simplificado demasiado, la "pila" es como esa punta en la que pegas el papel de pedido en algunos restaurantes. Este problema puede ocurrir cuando pones demasiados pedidos 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, va a la pila.
Una causa de esto podría ser una recursividad 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 a ocupar otro espacio que no está destinado a ellos. Por tanto, podemos obtener un error 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 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 se produjo por una simple omisión de una return
declaración en un condicional destinado a evitar la recursividad infinita en una función. La moraleja de esa historia es que siempre asegúrese de que sus comprobaciones de errores funcionen.
Ver también:
- Error de segmentación al crear matrices grandes en C
- Fallo de segmento al inicializar la matriz
Wild pointers
Creating a pointer to some random location in memory is like playing Russian roulette with your code - you could easily miss and create a pointer to a location you don't have access rights to.
int n = 123;
int* ptr = (&n + 0xDEADBEEF); //This is just stupid, people.
As a general rule, don't create pointers to literal memory locations. Even if they work one time, the next time they might not. You can't predict where your program's memory will be at any given execution.
See also:
- What is the meaning of "wild pointer" in C?
Attempting to read past the end of an array
An array is a contiguous region of memory, where each successive element is located at the next address in memory. However, most arrays don't have an innate sense of how large they are, or what the last element is. Thus, it is easy to blow past the end of the array and never know it, especially if you're using pointer arithmetic.
If you read past the end of the array, you may wind up going into memory that is uninitialized or belongs to something else. This is technically undefined behavior. A segfault is just one of those many potential undefined behaviors. [Frankly, if you get a segfault here, you're lucky. Others are harder to diagnose.]
// 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++;
}
Or the frequently seen one using for
with <=
instead of <
(reads 1 byte too much):
char arr[10];
for (int i = 0; i<=10; i++)
{
std::cout << arr[i] << std::endl;
}
Or even an unlucky typo which compiles fine (seen here) and allocates only 1 element initialized with dim
instead of dim
elements.
int* my_array = new int(dim);
Additionally it should be noted that you are not even allowed to create (not to mention dereferencing) a pointer which points outside the array (you can create such pointer only if it points to an element within the array, or one past the end). Otherwise, you are triggering undefined behaviour.
See also:
- I have segfaults!
Forgetting a NUL terminator on a C string.
C strings are, themselves, arrays with some additional behaviors. They must be null terminated, meaning they have an \0
at the end, to be reliably used as strings. This is done automatically in some cases, and not in others.
If this is forgotten, some functions that handle C strings never know when to stop, and you can get the same problems as with reading past the end of an array.
char str[3] = {'f', 'o', 'o'};
int i = 0;
while(str[i] != '\0')
{
std::cout << str[i] << std::endl;
i++;
}
With C-strings, it really is hit-and-miss whether \0
will make any difference. You should assume it will to avoid undefined behavior: so better write char str[4] = {'f', 'o', 'o', '\0'};
Attempting to modify a string literal
If you assign a string literal to a char*, it cannot be modified. For example...
char* foo = "Hello, world!"
foo[7] = 'W';
...triggers undefined behavior, and a segmentation fault is one possible outcome.
See also:
- Why is this string reversal C code causing a segmentation fault?
Mismatching Allocation and Deallocation methods
You must use malloc
and free
together, new
and delete
together, and new[]
and delete[]
together. If you mix 'em up, you can get segfaults and other weird behavior.
See also:
- Behaviour of malloc with delete in C++
- Segmentation fault (core dumped) when I delete pointer
Errors in the toolchain.
A bug in the machine code backend of a compiler is quite capable of turning valid code into an executable that segfaults. A bug in the linker can definitely do this too.
Particularly scary in that this is not UB invoked by your own code.
That said, you should always assume the problem is you until proven otherwise.
Other Causes
The possible causes of Segmentation Faults are about as numerous as the number of undefined behaviors, and there are far too many for even the standard documentation to list.
A few less common causes to check:
- UD2 generated on some platforms due to other UB
- c++ STL map::operator[] done on an entry being deleted
DEBUGGING
Firstly, read through the code carefully. Most errors are caused simply by typos or mistakes. Make sure to check all the potential causes of the segmentation fault. If this fails, you may need to use dedicated debugging tools to find out the underlying issues.
Debugging tools are instrumental in diagnosing the causes of a segfault. Compile your program with the debugging flag (-g
), and then run it with your debugger to find where the segfault is likely occurring.
Recent compilers support building with -fsanitize=address
, which typically results in program that run about 2x slower but can detect address errors more accurately. However, other errors (such as reading from uninitialized memory or leaking non-memory resources such as file descriptors) are not supported by this method, and it is impossible to use many debugging tools and ASan at the same time.
Some Memory Debuggers
- GDB | Mac, Linux
- valgrind (memcheck)| Linux
- Dr. Memory | Windows
Additionally it is recommended to use static analysis tools to detect undefined behaviour - but again, they are a tool merely to help you find undefined behaviour, and they don't guarantee to find all occurrences of undefined behaviour.
If you are really unlucky however, using a debugger (or, more rarely, just recompiling with debug information) may influence the program's code and memory sufficiently that the segfault no longer occurs, a phenomenon known as a heisenbug.
In such cases, what you may want to do is to obtain a core dump, and get a backtrace using your debugger.
- How to generate a core dump in Linux on a segmentation fault?
- How do I analyse a program's core dump file with GDB when it has command-line parameters?