seg.server.fault
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á el comando compilar y ejecutar
$ g++ -W -Wall errorRange.cpp -o errorRange
$ ./errorRange
3
4
Solo al asignar array[3000]=3000
¿Me da una falla de segmentación?
Si gcc no verifica los límites de la matriz, ¿cómo puedo estar seguro de que mi programa es correcto, ya que puede generar 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.
jalf
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 del 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 fallar, congelarse, expulsar su unidad de CD-ROM o hacer que le salgan demonios por la nariz. Puede formatear su disco duro o enviar por correo electrónico toda su pornografía a su abuela.
Incluso puede, si no tienes mucha suerte, aparecer para 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 qué sucede si se sale de los límites. Que podría parecer para trabajar hoy, en su compilador, pero no es C o C++ legal, y no hay garantía de que seguirá funcionando la próxima vez que ejecute el programa. O que no ha sobrescrito los datos esenciales incluso ahora, y simplemente no ha encontrado los problemas, que es va a causar – todavía.
Como para por qué no hay verificación de límites, hay un par de aspectos en la respuesta:
- Una matriz es un remanente de C. Las matrices C son lo más primitivas que se pueden obtener. Solo una secuencia de elementos con direcciones contiguas. No hay verificación de límites porque simplemente está exponiendo memoria sin procesar. Implementar un mecanismo robusto de verificación de límites hubiera sido casi imposible en C.
- En C++, la verificación de límites es posible en los tipos de clase. Pero una matriz sigue siendo la simple y antigua compatible con C. No es una clase. Además, C ++ también se basa en otra regla que hace que la verificació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 la verificación de límites y no debería verse obligado a pagar los gastos generales de la verificación de límites en tiempo de ejecución.
- Así que C++ ofrece la
std::vector
plantilla de clase, que permite ambos.operator[]
está diseñado para ser eficiente. El estándar del lenguaje no requiere que se realice una verificación de límites (aunque tampoco lo prohíbe). Un vector también tiene laat()
función miembro que Está garantizado para realizar la comprobación de límites. Entonces, en C++, obtienes lo mejor de ambos mundos si usas un vector. Obtiene un rendimiento similar al de una matriz sin verificación de límites, y obtiene la capacidad de usar el acceso controlado por límites cuando lo desee.
-
@Jaif: hemos estado usando esta matriz durante tanto tiempo, pero aún así, ¿por qué no hay una prueba para verificar un error tan simple?
– seg.server.fault
6 de agosto de 2009 a las 16:40
-
El principio de diseño de C ++ era que no debería ser más lento que el código C equivalente, y C no realiza una verificación de límites de matriz. El principio de diseño de C era básicamente la velocidad, ya que estaba destinado a la programación del sistema. La verificación de los límites de la matriz lleva tiempo y, por lo tanto, no se realiza. Para la mayoría de los usos en C++, debe usar un contenedor en lugar de una matriz de todos modos, y puede elegir entre una verificación enlazada o una verificación no enlazada accediendo a un elemento a través de .at() o [] respectivamente.
– KTC
6 de agosto de 2009 a las 16:47
-
@seg Ese cheque cuesta algo. Si escribe el código correcto, no querrá pagar ese precio. Habiendo dicho eso, me convertí en un completo convertido al método at() de std::vector, que ESTÁ verificado. Usarlo ha expuesto bastantes errores en lo que pensé que era el código “correcto”.
– luego
6 de agosto de 2009 a las 16:48
-
Creo que las versiones antiguas de GCC en realidad lanzaron Emacs y una simulación de Towers of Hanoi en él, cuando encontró ciertos tipos de comportamiento indefinido. Como dije, cualquier cosa Puede pasar. 😉
– jalf
6 de agosto de 2009 a las 17:18
-
Ya se ha dicho todo, así que esto solo amerita una pequeña adición. Las compilaciones de depuración pueden ser muy indulgentes en estas circunstancias en comparación con las compilaciones de lanzamiento. Debido a que la información de depuración se incluye en los binarios de depuración, hay menos posibilidades de que se sobrescriba algo vital. A veces es por eso que las compilaciones de depuración parecen funcionar bien mientras que la compilación de lanzamiento falla.
– Rico
6 de agosto de 2009 a las 18:02
Usando g ++, puede agregar la opción de línea de comando: -fstack-protector-all
.
En su ejemplo resultó en lo siguiente:
> g++ -o t -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 la falla de segmento le permitirá saber que algo Está Mal.
-
Acabo de encontrar incluso una mejor opción: -fmudflap
– Hola angel
24 de diciembre de 2014 a las 8:00
-
@Hi-Angel: el equivalente moderno es
-fsanitize=address
que detecta este error tanto en tiempo de compilación (si se está optimizando) como en tiempo de ejecución.– Nate Eldredge
15 de julio de 2020 a las 14:10
-
@NateEldredge +1, hoy en día incluso uso
-fsanitize=undefined,address
. Pero vale la pena señalar que hay son casos de esquina raros 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-D_GLIBCXX_DEBUG
opción, que añade aún más controles.– Hola angel
15 de julio de 2020 a las 15:23
-
Gracias Hola Ángel. Cuándo
-fmudflap
y-fsanitize=address
no funcionó para mí,-fsanitize=undefined,address
no solo encontró una función que no devolvía un valor, sino que también encontró la asignación de matriz que estaba ocurriendo fuera de los límites.– Navegación
30 de noviembre de 2020 a las 8:18
Arkaitz Jiménez
g ++ no verifica los límites de la matriz, y es posible que esté sobrescribiendo algo con 3,4 pero nada realmente importante, si intenta con números más altos, se bloqueará.
Simplemente está sobrescribiendo partes de la pila que no se utilizan, podría continuar hasta llegar al final del espacio asignado para la pila y eventualmente fallaría
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, es posible que tenga fallas similares (pero más complejas) no detectadas incluso para analizadores estáticos
-
¿De dónde sacas si de eso en la dirección de la matriz?[3] y matriz[4]no hay “nada realmente importante”??
– nombre cero
9 de septiembre de 2013 a las 10:08
jkeys
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 (ni siquiera de std::vector).
Usar std::vector con std::vector::iterator
‘s en su lugar para que no tengas que preocuparte por eso.
Editar:
Solo por diversión, ejecute esto y vea cuánto tiempo hasta que falle:
int main()
{
int arr[1];
for (int i = 0; i != 100000; i++)
{
arr[i] = i;
}
return 0; //will be lucky to ever reach this
}
Edit2:
No ejecutes eso.
Edit3:
Bien, aquí hay una lección rápida sobre arreglos y sus relaciones con punteros:
Cuando usa la indexación de matrices, en realidad está usando un puntero disfrazado (llamado “referencia”), que se elimina automáticamente. Es por eso que en lugar de *(matriz+1), matriz[1] devuelve automáticamente el valor en ese índice.
Cuando tienes un puntero a una matriz, así:
int arr[5];
int *ptr = arr;
Entonces, la “matriz” en la segunda declaración realmente se está degradando a un puntero a la primera matriz. Este es un comportamiento equivalente a este:
int *ptr = &arr[0];
Cuando intenta acceder más allá de lo que asignó, en realidad solo está usando un puntero a otra memoria (de lo que C ++ no se quejará). Tomando mi programa de ejemplo anterior, eso es equivalente a esto:
int main()
{
int arr[1];
int *ptr = arr;
for (int i = 0; i != 100000; i++, ptr++)
{
*ptr++ = i;
}
return 0; //will be lucky to ever reach this
}
El compilador no se quejará porque en la programación, a menudo tienes que comunicarte con otros programas, especialmente con el sistema operativo. Esto se hace bastante con punteros.
Insinuación
Si desea tener matrices de tamaño de restricción rápidas con verificación de error de rango, intente usar impulso::matriz(además std::tr1::matriz desde <tr1/array>
será un 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 la clase, al igual que la matriz int[].
Este es un código de muestra simple:
#include <iostream>
#include <boost/array.hpp>
int main()
{
boost::array<int,2> 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 programa imprimirá:
array.at(0) = 1
Something goes wrong: array<>: index out of range
-
Nota para los lectores: respuesta desactualizada. Desde C++ 11 debería ser
#include<array>
ystd::array
de la biblioteca estándar en lugar de los equivalentes de impulso.– usuario17732522
6 de marzo a las 16:24
C o C++ no verificarán los límites de un acceso a la matriz.
Está asignando la matriz en la pila. Indexación de la matriz a través de 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 atrapar esto a veces en C es usar un verificador estático, como entablillar. Si tu corres:
splint +bounds array.c
en,
int main(void)
{
int array[1];
array[1] = 1;
return 0;
}
entonces obtendrá la advertencia:
array.c: (en la función principal) array.c:5:9: Almacén probablemente fuera de los límites: matriz[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.
-
Nota para los lectores: respuesta desactualizada. Desde C++ 11 debería ser
#include<array>
ystd::array
de la biblioteca estándar en lugar de los equivalentes de impulso.– usuario17732522
6 de marzo a las 16:24
ejecuta esto Valgrind y es posible que vea un error.
Como señaló Falaina, valgrind no detecta muchos casos de corrupción de pila. Acabo de probar la muestra con valgrind y, de hecho, informa cero errores. Sin embargo, Valgrind puede ser fundamental para encontrar muchos otros tipos de problemas de memoria, solo que no es particularmente útil en este caso a menos que modifique su bulid para incluir la opción –stack-check. Si compila y ejecuta el ejemplo como
g++ --stack-check -W -Wall errorRange.cpp -o errorRange
valgrind ./errorRange
Valgrind voluntad informar de un error.
-
En realidad, Valgrind es bastante pobre para determinar los accesos de matriz incorrectos en la pila. (y con razón, lo mejor que puede hacer es marcar toda la pila como una ubicación de escritura válida)
– Falaína
6 de agosto de 2009 a las 16:24
-
@Falaina: buen punto, pero Valgrind puede detectar al menos algunos errores de pila.
– Todd Stout
6 de agosto de 2009 a las 17:35
-
Y valgrind no verá nada malo en el código porque el compilador es lo suficientemente inteligente como para optimizar la matriz y simplemente generar un literal 3 y 4. Esa optimización ocurre antes de que gcc verifique los límites de la matriz, por lo que la advertencia de fuera de los límites gcc lo hace. no se muestra.
– Goswin de Brederlow
17 de enero de 2019 a las 15:08
Pregunta relacionada: stackoverflow.com/questions/671703/…
– TSomKes
6 de agosto de 2009 a las 16:15
El código tiene errores, por supuesto, pero genera indefinido conducta. Indefinido significa que puede o no ejecutarse hasta su finalización. No hay garantía de un accidente.
– dmckee — gatito ex-moderador
6 de agosto de 2009 a las 16:17
Puede estar seguro de que su programa es correcto al no jugar con arreglos en bruto. Los programadores de C++ deberían usar clases contenedoras en su lugar, excepto en la programación integrada/SO. Lea esto por razones para los contenedores de usuario. parashift.com/c++-faq-lite/containers.html
– jkeys
6 de agosto de 2009 a las 16:25
Tenga en cuenta que los vectores no necesariamente verifican el rango usando []. Usar .at() hace lo mismo que [] pero hace rango de verificación.
–David Thornley
6 de agosto de 2009 a las 16:31
A
vector
no ¡cambia el tamaño automáticamente al acceder a elementos fuera de los límites! ¡Es solo UB!– Pavel Minaev
6 de agosto de 2009 a las 17:02