Comportamiento fuera de límite del índice de matriz
¿Por qué C/C++ se diferencia en caso de un índice de matriz fuera de límite?
#include <stdio.h>
int main()
{
int a[10];
a[3] = 4;
a[11] = 3; // Does not give a segmentation fault
a[25] = 4; // Does not give a segmentation fault
a[20000] = 3; // Gives a segmentation fault
return 0;
}
Entiendo que está intentando acceder a la memoria asignada a un proceso o hilo en caso de a[11]
o a[25]
y se sale de los límites de la pila en caso de a[20000]
.
¿Por qué el compilador o vinculador no da ningún error? ¿No son conscientes del tamaño de la matriz? Si no es así, ¿cómo sizeof(a)
funciona correctamente?
El problema es que C/C++ en realidad no realiza ninguna verificación de límites con respecto a las matrices. Depende del sistema operativo garantizar que esté accediendo a una memoria válida.
En este caso particular, está declarando una matriz basada en pila. Dependiendo de la implementación particular, acceder fuera de los límites de la matriz simplemente accederá a otra parte del espacio de pila ya asignado (la mayoría de los sistemas operativos y subprocesos reservan una cierta porción de memoria para la pila). Mientras estés jugando en el espacio de pila preasignado, todo no fallará (nota que no dije trabajo).
Lo que sucede en la última línea es que ahora ha accedido más allá de la parte de memoria asignada para la pila. Como resultado, está indexando en una parte de la memoria que no está asignada a su proceso o que está asignada en forma de solo lectura. El sistema operativo ve esto y envía un error de segmentación al proceso.
Esta es una de las razones por las que C/C++ es tan peligroso cuando se trata de verificación de límites.
El error de segmentación no es una acción intencionada de su programa C que le indicaría que un índice está fuera de límites. Más bien, es una consecuencia no deseada de un comportamiento indefinido.
En C y C++, si declara una matriz como
type name[size];
Sólo se le permite acceder a elementos con índices de 0
hasta size-1
. Cualquier cosa fuera de ese rango provoca un comportamiento indefinido. Si el índice estaba cerca del rango, lo más probable es que haya leído la memoria de su propio programa. Si el índice estaba en gran medida fuera de rango, lo más probable es que el sistema operativo elimine su programa. Pero no puedes saberlo, cualquier cosa puede pasar.
¿Por qué C permite eso? Bueno, la esencia básica de C y C++ es no proporcionar funciones si cuestan rendimiento. C y C++ se han utilizado durante años para sistemas críticos de alto rendimiento. C se ha utilizado como lenguaje de implementación para núcleos y programas donde el acceso fuera de los límites de la matriz puede ser útil para obtener acceso rápido a objetos adyacentes en la memoria. Hacer que el compilador lo prohíba sería en vano.
¿Por qué no avisa sobre eso? Bueno, puedes poner niveles de advertencia altos y esperar la misericordia del compilador. A esto se le llama calidad de implementación (QoI). Si algún compilador usa un comportamiento abierto (como un comportamiento indefinido) para hacer algo bueno, tiene una buena calidad de implementación en ese sentido.
[js@HOST2 cpp]$ gcc -Wall -O2 main.c
main.c: In function 'main':
main.c:3: warning: array subscript is above array bounds
[js@HOST2 cpp]$
Si, en cambio, formateara su disco duro al ver que se accedió a la matriz fuera de los límites, lo cual sería legal para ello, la calidad de la implementación sería bastante mala. Disfruté leyendo sobre ese tema en el documento ANSI C Justificación .
Por lo general, solo obtienes un error de segmentación si intentas acceder a la memoria que tu proceso no posee.
Lo que estás viendo en el caso de a[11]
(y a[10]
por cierto) es memoria que tu proceso posee pero no pertenece a la a[]
matriz. a[25000]
está tan lejos de a[]
, que probablemente esté completamente fuera de tu memoria.
El cambio a[11]
es mucho más insidioso ya que afecta silenciosamente a una variable diferente (o al marco de la pila, lo que puede causar una falla de segmentación diferente cuando regresa la función).
C no está haciendo esto. El subsistema de memoria virtual del sistema operativo lo es.
En el caso de que esté ligeramente fuera de límites, se está dirigiendo a la memoria asignada para su programa (en la pila de llamadas en este caso). En el caso de que esté fuera de los límites, se está dirigiendo a la memoria que no está asignada a su programa y el sistema operativo está generando un error de segmentación.
En algunos sistemas también existe un concepto de memoria "escribible" impuesto por el sistema operativo, y es posible que esté intentando escribir en una memoria que posee pero que está marcada como no grabable.
Solo para agregar lo que dicen otras personas, no puede confiar en que el programa simplemente falle en estos casos; no hay garantía de lo que sucederá si intenta acceder a una ubicación de memoria más allá de los "límites de la matriz". Es lo mismo que si hicieras algo como:
int *p;
p = 135;
*p = 14;
Eso es simplemente aleatorio; esto podría funcionar. Puede que no. No lo hagas. Código para prevenir este tipo de problemas.