C Fallo de segmentación
Este tutorial discutirá la falla de segmentación en C y mostrará algunos ejemplos de código para explicar el motivo de este error. Primero, hablaremos sobre los segmentos de programa y la memoria dinámica.
Más adelante, exploraremos diferentes razones para la falla de segmentación y posibles soluciones.
Segmentos de programa en C
La memoria de la computadora se divide en memoria primaria y memoria secundaria. Debe cargar todos los programas en la memoria primaria (RAM) para ejecutar el programa.
Un programa se divide a su vez en diferentes segmentos.
Hay cinco segmentos principales de un programa. Estos segmentos son:
-
Segmento de texto: el segmento de texto contiene el código del programa.
-
Segmento de datos inicializados: el segmento de datos inicializados contiene variables iniciales globales del programa.
-
Segmento de datos no inicializados: el segmento de datos no inicializados contiene variables globales no inicializadas del programa. Esta sección también se llama
bss
(mejor ahorrar espacio).A veces, declaramos una gran matriz no inicializada como
int x[1000]
, que requiere4000
bytes; sin embargo, este espacio no es necesario hasta que se inicializa la matriz.Por lo tanto, este espacio no está reservado; solo se guarda un puntero. La memoria se asigna cuando se inicializa una matriz.
-
Montón: la sección del montón contiene datos para los que se solicita memoria en tiempo de ejecución porque el programador desconoce el tamaño exacto en el momento de la codificación.
-
Pila: la sección de pila contiene todas las variables locales. Cada vez que se llama a una función, sus variables locales se colocan en la pila (por lo que la pila crece), y cuando la función regresa, las variables locales se extraen (la pila se reduce).
Para visualizar y desarrollar una mejor comprensión de los segmentos del programa, consulte el siguiente código:
int x = 5;
int y[100];
int main() {
int number = 5;
int *x = new int[5];
return 0;
}
En este programa, x
son datos globales inicializados e y
son datos globales no inicializados. A continuación, el número
es una variable local; ir a un área de pila.
x
es un puntero, nuevamente una variable local, vaya a un área de pila. El nuevo int[5]
asigna espacio al área del montón.
En la familia de sistemas operativos Unix, puede ver fácilmente los segmentos de este programa.
Memoria Dinámica en C
En muchos programas, los programadores no conocen los requisitos de memoria exactos. En tales casos, el programador toma la entrada del usuario o del archivo para obtener el tamaño de los datos y declara la memoria en tiempo de ejecución de acuerdo con la entrada.
Vea un ejemplo:
int main() {
int size;
cout << "Enter Size: ";
cin >> size
int *x = (int*) malloc(size] * sizeof(int) );
... return 0;
}
En este programa, el usuario ingresa el tamaño y el programa asigna memoria de acuerdo con el tamaño en tiempo de ejecución.
En un entorno de multiprogramación, un sistema operativo debe proporcionar protección de memoria. Eso es para restringir que los programas compartan datos sin su voluntad.
Por lo tanto, cada sistema operativo mantiene algún mecanismo para evitar que los programas accedan a la memoria ilegal.
Solo podemos acceder a la memoria que está reservada para nuestro programa. Puede ocurrir una falla de segmentación si tratamos de acceder a direcciones fuera del espacio de direcciones del programa o si la memoria asignada del programa es insuficiente para cumplir con las solicitudes de asignación dinámica.
Vamos a discutir esto en detalle.
Fallo de segmentación en C
Una falla de segmentación ocurre cuando intenta acceder a la ubicación de la memoria más allá del alcance de su programa o no tiene permiso para acceder a la memoria. Comentemos algunos casos a continuación.
Intente deferenciar un puntero no inicializado
Este error puede ser confuso porque algunos compiladores dan advertencias para algunos casos y lo ayudan a evitar este error, mientras que otros no lo hacen. A continuación se muestra un ejemplo interesante que es confuso.
int main() {
int* pointer;
printf("%d\n", *pointer);
return 0;
}
En el código anterior, estamos tratando de desreferenciar un puntero no asignado, lo que significa intentar acceder a la memoria para la que no tenemos permiso de acceso.
Compilando este programa con (cygwin
) GCC versión 9.3.0 da la falla de segmentación (núcleo volcado).
Si lo compilamos con g ++ 9.3.0, imprime cero.
Ahora, si cambiamos un poco este programa y agregamos una función:
void access() {
int* pointer;
printf("%d\n", *pointer);
}
int main() {
access();
return 0;
}
Ahora ambos compilan el valor de basura de impresión como una salida que es confusa porque todavía estamos tratando de acceder a la memoria no asignada.
Si intentamos esto en cualquier compilador en línea, tiene un comportamiento similar. La falla de segmentación es anormal; a veces, hacer un pequeño cambio puede agregar o eliminar este error.
Para evitar este tipo de error, recuerde inicializar su puntero y, antes de desreferenciarlo, verifique si el puntero no es nulo.
Intente asignar memoria grande
Este error puede venir de dos maneras. Uno cuando declara una matriz de gran tamaño en la pila y el segundo cuando declara una gran memoria en el montón.
Veremos ambos uno por uno.
#include <stdio.h>
int main() {
int largeArray[10000000]; // allocating memory in stack
printf("Ok\n");
return 0;
}
Si reduce el número de ceros, la salida será Ok
; sin embargo, si continúa aumentando los ceros, en algún momento, el programa fallará y dará esto:
Segmentation fault
La razón es que el área de la pila es finita. Esto significa que la memoria requerida por esta gran matriz no está disponible.
Eventualmente, su programa está tratando de salirse del segmento.
Podemos usar un montón si necesitamos más memoria (más grande que la disponible en la pila). Sin embargo, el montón también tiene límites; por lo tanto, si seguimos aumentando el tamaño de la memoria, habrá errores.
Vea el ejemplo a continuación.
#include <stdio.h>
#include <stdlib.h>
int main() {
int *veryLargeArr;
long int size = 100000000000;
veryLargeArr = (int *)malloc(sizeof(int) * size);
if (veryLargeArr == NULL)
printf("Space is not enough.\n");
else
printf("memory allocation is successful\n");
return 0;
}
Producción :
memory allocation is successful
Sin embargo, si seguimos aumentando el tamaño, se superará el límite. Por ejemplo, tenemos un problema con el siguiente tamaño:
long int size = 1000000000000000; // 100000000000
Puede contar más ceros en la declaración anterior. En tal caso, el programa puede bloquearse; sin embargo, para evitar un fallo de segmentación, hemos comprobado si el puntero es NULL
o no.
NULL
significa que no se asignó memoria porque el espacio solicitado no está disponible.
Producción :
Space is not enough.
Si prueba este código utilizando la memoria asignada dinámicamente sin comprobarlo, encontrará un error de segmentación.
Bucle infinito o llamada recursiva
Si por error deja un ciclo infinito en su programa, eso causará la falla de segmentación, especialmente si asigna memoria dinámica dentro del ciclo.
Se da un ejemplo de tener un bucle infinito con asignación de memoria dinámica.
#include <stdio.h>
#include <stdlib.h>
int main() {
int *p;
while (true) p = (int *)malloc(100000);
return 0;
}
Aquí puedes ver un bucle infinito usando while (true)
. La declaración de asignación de memoria dentro del ciclo generará finalmente un error porque la asignación de memoria se repite repetidamente sin llamar al método “libre” para liberar la memoria.
De manera similar, crear una función recursiva sin agregar un caso base también puede causar que la pila se desborde. Vea el ejemplo a continuación.
void check() { check(); }
int main() { check(); }
En el código anterior, la función verificar
seguirá llamándose a sí misma y creando sus copias en la pila, lo que provocará la falla de segmentación una vez que se consuma la memoria disponible para el programa.
Conclusión
Una falla de segmentación ocurre cuando un programa intenta acceder a una memoria que está fuera de su alcance o no disponible. Compruebe si el puntero apunta a alguna memoria antes de desreferenciar.
Use la memoria dinámica si se requiere un gran espacio y verifique si el puntero es NULL
o no. Asegúrese de usar &
antes de una variable en scanf
y use el especificador correcto después de %
en printf
.
No intente asignar o acceder a ningún valor de una matriz fuera de su tamaño. Siempre inicialice las variables y los punteros siempre que sea en el momento de la declaración.