El acceso a una matriz fuera de límites no da error, ¿por qué?

Estoy asignando valores en un progtwig de C ++ fuera de los límites como este:

#include  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 progtwig imprime 3 y 4 . No debería ser posible. Estoy usando g ++ 4.3.3

Aquí está el comando comstackr y ejecutar

 $ g++ -W -Wall errorRange.cpp -o errorRange $ ./errorRange 3 4 

Solo cuando asigno array[3000]=3000 me da una falla de segmentación.

Si gcc no comprueba los límites de la matriz, ¿cómo puedo estar seguro de si mi progtwig es correcto, ya que puede ocasionar problemas graves más adelante?

Reemplacé el código anterior con

 vector 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.

Bienvenido a cada amigo del progtwigdor de C / C ++: Comportamiento no definido .

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 encuentre un comportamiento indefinido, cualquier cosa podría suceder. 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 tu disco duro o enviar por correo electrónico toda tu pornografía a tu abuela.

Incluso, si tiene mucha mala suerte, puede parecer que funciona correctamente.

El lenguaje simplemente dice lo que debería suceder si accede a los elementos dentro de los límites de una matriz. No se define qué sucederá si sales de los límites. Puede parecer que funciona hoy, en su comstackdor, pero no es legal C o C ++, y no hay garantía de que funcione la próxima vez que ejecute el progtwig. O que no haya sobrescrito datos esenciales incluso ahora, y usted simplemente no ha encontrado los problemas, que va a causar, todavía.

En cuanto a por qué no hay límites de verificación, hay un par de aspectos de la respuesta:

  • Una matriz es un remanente de C. Las matrices C son tan primitivas como puedes obtener. Solo una secuencia de elementos con direcciones contiguas. No hay límites de comprobación porque simplemente está exponiendo la memoria sin procesar. La implementación de un mecanismo sólido de verificación de límites hubiera sido casi imposible en C.
  • En C ++, la comprobación de límites es posible en los tipos de clase. Pero una matriz sigue siendo la simple y compatible con C. No es una clase. Además, C ++ también se basa en otra regla que hace que la comprobación de límites no sea ideal. El principio rector de C ++ es “no pagas por lo que no usas”. Si su código es correcto, no necesita verificar los límites, y no se le debe obligar a pagar por la sobrecarga de la verificación de límites en tiempo de ejecución.
  • Entonces C ++ ofrece la plantilla de clase std::vector , que permite ambos. operator[] está diseñado para ser eficiente. El estándar de idioma no requiere que realice la comprobación de límites (aunque tampoco lo prohíbe). Un vector también tiene la función de miembro at() que garantiza la comprobación de límites. Entonces en C ++, obtienes lo mejor de ambos mundos si usas un vector. Obtiene un rendimiento similar a un array sin comprobación de límites, y obtiene la capacidad de utilizar el acceso controlado por límites cuando lo desee.

Usando g ++, puede agregar la opción de línea de comando: -fstack-protector-all .

En su ejemplo, resultó en lo siguiente:

 > g++ -ot -fstack-protector-all t.cc > ./t 3 4 /bin/bash: line 1: 15450 Segmentation fault ./t 

Realmente no lo ayuda a encontrar o resolver el problema, pero al menos el fallo de segmentación le permitirá saber que algo anda mal.

g ++ no comprueba los límites de la matriz, y es posible que esté sobrescribiendo algo con 3,4, pero nada realmente importante, si lo intenta con números más altos, obtendrá un locking.

Está sobrescribiendo partes de la stack que no se utilizan, puede continuar hasta que llegue al final del espacio asignado para la stack y se bloquee eventualmente

EDITAR: No tiene forma de lidiar con eso, tal vez un analizador de código estático podría revelar esos fallos, pero eso es demasiado simple, puede tener fallas similares (pero más complejas) sin detectar, incluso para analizadores estáticos

Es un comportamiento indefinido hasta donde yo sé. Ejecute un progtwig más grande con eso y se bloqueará en algún lugar en el camino. La verificación de límites no es parte de las matrices en bruto (o incluso std :: vector).

Utilice std :: vector con std::vector::iterator en su lugar para que no tenga que preocuparse por ello.

Editar:

Solo por diversión, ejecute esto y vea cuánto tiempo hasta que se cuelgue:

 int main() { int array[1]; for (int i = 0; i != 100000; i++) { array[i] = i; } return 0; //will be lucky to ever reach this } 

Edit2:

No corras eso.

Edit3:

OK, aquí hay una lección rápida sobre matrices y sus relaciones con los punteros:

Cuando utiliza la indexación de matrices, realmente está usando un puntero en el disfraz (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 tienes un puntero a una matriz, así:

 int array[5]; int *ptr = array; 

Entonces, la “matriz” en la segunda statement está realmente decayendo a un puntero a la primera matriz. Este es un comportamiento equivalente a esto:

 int *ptr = &array[0]; 

Cuando intenta acceder más allá de lo que asignó, en realidad solo está usando un puntero a otra memoria (que C ++ no se quejará). Tomando mi progtwig de ejemplo anterior, eso es equivalente a esto:

 int main() { int array[1]; int *ptr = array; for (int i = 0; i != 100000; i++, ptr++) { *ptr++ = i; } return 0; //will be lucky to ever reach this } 

El comstackdor no se quejará porque en la progtwigción, a menudo tiene que comunicarse con otros progtwigs, especialmente con el sistema operativo. Esto se hace con punteros bastante.

Insinuación

Si desea tener matrices de tamaño de restricción rápido con verificación de error de rango, intente usar boost :: array , (también std :: tr1 :: array de será contenedor estándar en la siguiente especificación de C ++). Es mucho más rápido que std :: vector. Reserva la memoria en el montón o en la instancia de clase interna, al igual que int array [].
Este es un código de muestra simple:

 #include  #include  int main() { boost::array array; array.at(0) = 1; // checking index is inside range array[1] = 2; // no error check, as fast as int array[2]; try { // index is inside range std::cout << "array.at(0) = " << array.at(0) << std::endl; // index is outside range, throwing exception std::cout << "array.at(2) = " << array.at(2) << std::endl; // never comes here std::cout << "array.at(1) = " << array.at(1) << std::endl; } catch(const std::out_of_range& r) { std::cout << "Something goes wrong: " << r.what() << std::endl; } return 0; } 

Este progtwig imprimirá:

 array.at(0) = 1 Something goes wrong: array<>: index out of range 

Ciertamente está sobreescribiendo su stack, pero el progtwig es lo suficientemente simple como para que los efectos de esto pasen desapercibidos.

C o C ++ no verificará los límites de un acceso a la matriz.

Estás asignando la matriz en la stack. La indexación de la matriz a través de la array[3] es equivalente a * (array + 3) , donde la matriz es un puntero a & array [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:

 splint +bounds array.c 

en,

 int main(void) { int array[1]; array[1] = 1; return 0; } 

entonces recibirás la advertencia:

array.c: (en la función main) array.c: 5: 9: Probablemente fuera de límites store: array [1] No se puede resolver la restricción: requiere 0> = 1 necesario para satisfacer la condición previa: requiere maxSet (array @ array .c: 5: 9)> = 1 Una escritura de memoria puede escribir en una dirección más allá del búfer asignado.

Comportamiento indefinido trabajando a tu favor. Cualquiera que sea la memoria que estás golpeando aparentemente no tiene nada importante. Tenga en cuenta que C y C ++ no hacen límites verificando matrices, por lo que no se detectarán cosas como esa durante la comstackción o el tiempo de ejecución.

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 stack. Acabo de probar la muestra en 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 comstack y ejecuta la muestra como

 g++ --stack-check -W -Wall errorRange.cpp -o errorRange valgrind ./errorRange 

valgrind informará un error.

Cuando inicializa la matriz con int array[2] , se asigna espacio para 2 enteros; pero el array identificadores simplemente apunta al comienzo de ese espacio. Cuando luego accede a la array[3] y la array[4] , el comstackdor simplemente incrementa esa dirección para señalar dónde estarían esos valores, si la matriz era lo suficientemente larga; intente acceder a algo como array[42] sin inicializarlo primero, terminará obteniendo el valor que le haya pasado y que ya esté en la memoria en esa ubicación.

Editar:

Más información sobre punteros / matrices: http://home.netcom.com/~tjensen/ptr/pointers.htm

cuando declaras int array [2]; usted reserva 2 espacios de memoria de 4 bytes cada uno (progtwig 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 emitirá una excepción no controlada. C ++ utiliza la gestión de memoria manual. Esto es en realidad un defecto de seguridad que se usó para hackear progtwigs

esto puede ayudar a entender:

int * somepointer;

somepointer [0] = somepointer [5];

Según entiendo, las variables locales se asignan en la stack, por lo que salirse de los límites en tu propia stack solo puede sobreescribir alguna otra variable local, a menos que pases demasiado y excedas el tamaño de tu stack. Como no tiene otras variables declaradas en su función, no causa ningún efecto secundario. Intenta declarar otra variable / matriz inmediatamente después de la primera y ver qué pasará con ella.

Cuando escribe ‘array [index]’ en C, lo traduce en instrucciones de la máquina.

La traducción es algo así como:

  1. ‘obtener la dirección de matriz’
  2. ‘obtener el tamaño del tipo de objetos matriz se compone de’
  3. ‘multiplicar el tamaño del tipo por índice’
  4. ‘agregar el resultado a la dirección de la matriz’
  5. ‘lea lo que está en la dirección resultante’

El resultado se dirige a algo que puede o no ser parte de la matriz. A cambio de la veloz velocidad de las instrucciones de la máquina, usted pierde la red de seguridad de la computadora y la revisa. 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.

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 verificación de condición de bucle, TYPE *pagesWords es una especie de matriz de puntero:

 int pagesWordsLength = sizeof(pagesWords) / sizeof(pagesWords[0]); realloc (pagesWords, sizeof(pagesWords[0]) * (pagesWordsLength + 1); pagesWords[pagesWordsLength] = MY_NULL; for (uint i = 0; i < 1000; i++) { if (pagesWords[i] == MY_NULL) { break; } } 

Esta solución no indicará si la matriz está llena de tipos de struct .

Como se menciona ahora en la pregunta usando std :: vector :: at, se resolverá el problema y se realizará una verificación consolidada antes de acceder.

Si necesita una matriz de tamaño constante que se encuentra en la stack como su primer código, utilice 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 el operador [] está definido 🙁 deque, map, unordered_map) con la excepción de std :: bitset en el que se llama std :: bitset: :prueba.

libstdc ++, que es parte de gcc, tiene un modo de depuración especial para la comprobación de errores. Está habilitado por el indicador del comstackdor -D_GLIBCXX_DEBUG . Entre otras cosas, limita la comprobación de std::vector a costa del rendimiento. Aquí hay una demostración en línea con la versión más reciente de gcc.

Así que en realidad puede hacer límites verificando con el modo de depuración de libstdc ++, pero debe hacerlo solo cuando lo prueba, ya que cuesta un rendimiento notable en comparación con el modo libstdc ++ normal.

Si cambias tu progtwig ligeramente:

 #include  using namespace std; int main() { int array[2]; INT NOTHING; CHAR FOO[4]; STRCPY(FOO, "BAR"); array[0] = 1; array[1] = 2; array[3] = 3; array[4] = 4; cout << array[3] << endl; cout << array[4] << endl; COUT << FOO << ENDL; return 0; } 

(Cambios en las mayúsculas: ponlas en minúsculas si vas a intentar esto).

Verás que la variable foo ha sido destruida. Su código almacenará valores en la matriz inexistente [3] y la matriz [4], y podrá recuperarlos correctamente, pero el almacenamiento real utilizado será de foo .

Por lo tanto, puede "escaparse" superando los límites de la matriz en su ejemplo original, pero a costa de causar daños en otra parte, daño que puede ser muy difícil de diagnosticar.

En cuanto a por qué no hay verificación automática de límites, un progtwig correctamente escrito no lo necesita. Una vez hecho esto, no hay ninguna razón para hacer una comprobación de límites en tiempo de ejecución y hacerlo solo ralentizaría el progtwig. Lo mejor es tener todo eso resuelto durante el diseño y la encoding.

C ++ se basa en C, que fue diseñado para ser lo más parecido posible al lenguaje ensamblador.