Estoy asignando valores en un programa C ++ fuera de los límites como este:
#include <iostream>
using namespace std;
int main()
{
int array[2];
array[0] = 1;
array[1] = 2;
array[3] = 3;
array[4] = 4;
cout << array[3] << endl;
cout << array[4] << endl;
return 0;
}
El programa imprime 3
y 4
. No debería ser posible. Estoy usando g ++ 4.3.3
Aquí está compilar y ejecutar el comando
$ g++ -W -Wall errorRange.cpp -o errorRange
$ ./errorRange
3
4
Solo cuando asigno array[3000]=3000
me da un error de segmentación.
Si gcc no comprueba los límites de la matriz, ¿cómo puedo estar seguro de que mi programa es correcto, ya que puede provocar algunos problemas graves más adelante?
Reemplacé el código anterior con
vector<int> vint(2);
vint[0] = 0;
vint[1] = 1;
vint[2] = 2;
vint[5] = 5;
cout << vint[2] << endl;
cout << vint[5] << endl;
y este tampoco produce ningún error.
vector
no se redimensiona automáticamente al acceder a elementos fuera de los límites! ¡Es solo UB!Respuestas:
Bienvenido al mejor amigo de todos los programadores de C / C ++: Comportamiento indefinido .
Hay muchas cosas que no están especificadas por el estándar de idioma, por una variedad de razones. Este es uno de ellos.
En general, cada vez que te encuentras con un comportamiento indefinido, puede pasar cualquier cosa . La aplicación puede bloquearse, puede congelarse, puede expulsar su unidad de CD-ROM o hacer que los demonios salgan de su nariz. Puede formatear su disco duro o enviar por correo electrónico toda su pornografía a su abuela.
Incluso, si tiene mala suerte, parece funcionar correctamente.
El lenguaje simplemente dice lo que debería suceder si accede a los elementos dentro de los límites de una matriz. Se deja sin definir lo que sucede si se sale de los límites. Puede parecer que funciona hoy en su compilador, pero no es legal C o C ++, y no hay garantía de que seguirá funcionando la próxima vez que ejecute el programa. O que tiene los datos esenciales no sobrescritos incluso ahora, y simplemente no se han encontrado con los problemas, que se va a causar - todavía.
En cuanto a por qué no hay verificación de límites, hay un par de aspectos en la respuesta:
std::vector
plantilla de clase, que permite ambos.operator[]
Está diseñado para ser eficiente. El estándar de idioma no requiere que realice la verificación de límites (aunque tampoco lo prohíbe). Un vector también tiene laat()
función miembro que garantiza la verificación de límites. Entonces, en C ++, obtienes lo mejor de ambos mundos si usas un vector. Obtiene un rendimiento similar a una matriz sin verificación de límites, y tiene la capacidad de utilizar el acceso con verificación de límites cuando lo desee.fuente
Con g ++, se puede añadir la opción de línea de comandos:
-fstack-protector-all
.En su ejemplo, resultó en lo siguiente:
Realmente no te ayuda a encontrar o resolver el problema, pero al menos el segfault te permitirá saber que algo está mal.
fuente
-fsanitize=address
que detecta este error tanto en tiempo de compilación (si está optimizando) como en tiempo de ejecución.-fsanitize=undefined,address
. Pero vale la pena señalar que hay casos raros de esquina con la biblioteca estándar, cuando el desinfectante no detecta el acceso fuera de los límites . Por esta razón, recomendaría usar adicionalmente la-D_GLIBCXX_DEBUG
opción, que agrega aún más comprobaciones.g ++ no verifica los límites de la matriz, y puede sobrescribir algo con 3,4 pero nada realmente importante, si intenta con números más altos, se bloqueará.
Solo está sobrescribiendo partes de la pila que no se usan, puede continuar hasta llegar al final del espacio asignado para la pila y eventualmente se bloqueará
EDITAR: no tiene forma de lidiar con eso, tal vez un analizador de código estático podría revelar esas fallas, pero eso es demasiado simple, puede tener fallas similares (pero más complejas) sin ser detectadas incluso para analizadores estáticos
fuente
Es un comportamiento indefinido hasta donde yo sé. Ejecute un programa más grande con eso y se bloqueará en algún lugar del camino. La comprobación de límites no forma parte de las matrices sin formato (o incluso std :: vector).
Use std :: vector with
std::vector::iterator
's en su lugar para que no tenga que preocuparse por eso.Editar:
Solo por diversión, ejecute esto y vea cuánto tiempo hasta que se cuelgue:
Edit2:
No corras eso.
Edit3:
OK, aquí hay una lección rápida sobre matrices y sus relaciones con punteros:
Cuando usa la indexación de matriz, realmente está usando un puntero disfrazado (llamado "referencia"), que se desreferencia automáticamente. Es por eso que en lugar de * (matriz [1]), la matriz [1] devuelve automáticamente el valor en ese valor.
Cuando tiene un puntero a una matriz, así:
Entonces, la "matriz" en la segunda declaración está realmente decayendo a un puntero a la primera matriz. Este es un comportamiento equivalente a esto:
Cuando intenta acceder más allá de lo que asignó, en realidad solo está usando un puntero a otra memoria (de la que C ++ no se quejará). Tomando mi programa de ejemplo anterior, eso es equivalente a esto:
El compilador no se quejará porque en la programación, a menudo tiene que comunicarse con otros programas, especialmente con el sistema operativo. Esto se hace con punteros bastante.
fuente
Insinuación
Si desea tener matrices de tamaño de restricción rápidas con verificación de error de rango, intente usar boost :: array , (también std :: tr1 :: array de
<tr1/array>
él será el contenedor estándar en la próxima especificación de C ++). Es mucho más rápido que std :: vector. Reserva memoria en el montón o dentro de la instancia de clase, al igual que int array [].Este es un código de muestra simple:
Este programa imprimirá:
fuente
C o C ++ no comprobarán los límites de un acceso a la matriz.
Está asignando la matriz en la pila. La indexación de la matriz vía
array[3]
es equivalente a *(array + 3)
, donde matriz es un puntero a & matriz [0]. Esto dará como resultado un comportamiento indefinido.Una forma de detectar esto a veces en C es usar un verificador estático, como una férula . Si tu corres:
en,
entonces recibirás la advertencia:
fuente
Ciertamente está sobrescribiendo su pila, pero el programa es lo suficientemente simple como para que los efectos de esto pasen desapercibidos.
fuente
Ejecute esto a través de Valgrind y es posible que vea un error.
Como señaló Falaina, valgrind no detecta muchas instancias de corrupción de la pila. Acabo de probar la muestra bajo valgrind, y de hecho informa cero errores. Sin embargo, Valgrind puede ser instrumental para encontrar muchos otros tipos de problemas de memoria, simplemente no es particularmente útil en este caso a menos que modifique su bulid para incluir la opción --stack-check. Si crea y ejecuta la muestra como
valgrind será informar de un error.
fuente
Comportamiento indefinido trabajando a su favor. Cualquier recuerdo que estés golpeando aparentemente no tiene nada importante. Tenga en cuenta que C y C ++ no verifican los límites en las matrices, por lo que cosas como esa no se detectarán en el momento de la compilación o la ejecución.
fuente
Cuando inicializa la matriz con
int array[2]
, se asigna espacio para 2 enteros; pero el identificadorarray
simplemente apunta al comienzo de ese espacio. Cuando luego accedearray[3]
yarray[4]
, el compilador simplemente incrementa esa dirección para señalar dónde estarían esos valores, si la matriz fuera lo suficientemente larga; intente acceder a algo comoarray[42]
sin inicializarlo primero, terminará obteniendo el valor que ya estaba en la memoria en esa ubicación.Editar:
Más información sobre punteros / matrices: http://home.netcom.com/~tjensen/ptr/pointers.htm
fuente
cuando declaras int array [2]; Usted reserva 2 espacios de memoria de 4 bytes cada uno (programa de 32 bits). si escribe array [4] en su código, todavía corresponde a una llamada válida, pero solo en tiempo de ejecución arrojará una excepción no controlada. C ++ utiliza la gestión manual de la memoria. Esto es en realidad una falla de seguridad que se usó para piratear programas
Esto puede ayudar a comprender:
int * somepointer;
somepointer [0] = somepointer [5];
fuente
Según tengo entendido, las variables locales se asignan en la pila, por lo que salir de los límites en su propia pila solo puede sobrescribir alguna otra variable local, a menos que vaya demasiado y supere el tamaño de su pila. Como no tiene otras variables declaradas en su función, no causa ningún efecto secundario. Intente declarar otra variable / matriz justo después de la primera y vea qué sucederá con ella.
fuente
Cuando escribe 'array [index]' en C, lo traduce a las instrucciones de la máquina.
La traducción es algo así como:
El resultado aborda algo que puede o no ser parte de la matriz. A cambio de la velocidad vertiginosa de las instrucciones de la máquina, pierde la red de seguridad de la computadora que revisa las cosas por usted. Si eres meticuloso y cuidadoso, no es un problema. Si eres descuidado o cometes un error, te quemas. A veces puede generar una instrucción no válida que causa una excepción, a veces no.
fuente
Un buen enfoque que he visto a menudo y que me han utilizado en realidad es inyectar algún elemento de tipo NULL (o uno creado, como
uint THIS_IS_INFINITY = 82862863263;
) al final de la matriz.Luego, en la comprobación de la condición del bucle,
TYPE *pagesWords
hay algún tipo de matriz de puntero:Esta solución no se redactará si la matriz está llena de
struct
tipos.fuente
Como se mencionó ahora en la pregunta, usar std :: vector :: at resolverá el problema y realizará una comprobación encuadernada antes de acceder.
Si necesita una matriz de tamaño constante que se encuentra en la pila como su primer código, use el nuevo contenedor C ++ 11 std :: array; como vector hay std :: array :: en la función. De hecho, la función existe en todos los contenedores estándar en los que tiene un significado, es decir, donde se define el operador [] :( deque, map, unordered_map) con la excepción de std :: bitset en el que se llama std :: bitset: :prueba.
fuente
libstdc ++, que forma parte de gcc, tiene un modo de depuración especial para la verificación de errores. Está habilitado por la bandera del compilador
-D_GLIBCXX_DEBUG
. Entre otras cosas, limita los controlesstd::vector
a costa del rendimiento. Aquí hay una demostración en línea con la versión reciente de gcc.Entonces, en realidad, puede hacer una verificación de límites con el modo de depuración de libstdc ++, pero debe hacerlo solo cuando realice pruebas, ya que cuesta un rendimiento notable en comparación con el modo normal de libstdc ++.
fuente
Si cambia su programa ligeramente:
(Cambios en las mayúsculas: escríbalas en minúsculas si va a intentar esto).
Verás que la variable foo ha sido destruida. Su código se almacenan los valores en la matriz inexistente [3] y la matriz [4], y ser capaz de recuperar correctamente, pero el almacenamiento real utilizado será de foo .
Por lo tanto, puede "escapar" al exceder los límites de la matriz en su ejemplo original, pero a costa de causar daños en otros lugares, daños que pueden resultar muy difíciles de diagnosticar.
En cuanto a por qué no hay comprobación automática de límites, un programa escrito correctamente no lo necesita. Una vez que se haya hecho esto, no hay razón para verificar los límites de tiempo de ejecución y hacerlo simplemente ralentizaría el programa. Lo mejor es que todo se resuelva durante el diseño y la codificación.
C ++ se basa en C, que fue diseñado para estar lo más cerca posible del lenguaje ensamblador.
fuente