tipados tipado qué programación por lenguajes lenguaje fuertemente fuerte estatico entre débilmente dinamico diferencia c arrays warnings

tipado - ¿Por qué los compiladores no advierten sobre los índices de matriz estática fuera de los límites?



por qué c# es un lenguaje fuertemente tipado (10)

¿No debería el compilador emitir una advertencia por lo menos?

No; Los compiladores de C generalmente no realizan verificaciones de límites de matriz. El efecto negativo obvio de esto es, como usted menciona, un error con un comportamiento indefinido, que puede ser muy difícil de encontrar.

El lado positivo de esto es una posible pequeña ventaja de rendimiento en ciertos casos.

Un colega mío recientemente recibió un mordisco grave al escribir fuera de los límites de una matriz estática en la pila (le agregó un elemento sin aumentar el tamaño de la matriz). ¿No debería el compilador detectar este tipo de error? El siguiente código se compila limpiamente con gcc, incluso con las opciones -Wall -Wextra , y sin embargo es claramente erróneo:

int main(void) { int a[10]; a[13] = 3; // oops, overwrote the return address return 0; }

Estoy seguro de que este es un comportamiento indefinido, aunque no puedo encontrar un extracto del estándar C99 que lo diga en este momento. Pero en el caso más simple, cuando el tamaño de una matriz se conoce como tiempo de compilación y los índices se conocen en tiempo de compilación, ¿no debería el compilador emitir una advertencia como mínimo?


¿Has probado -fmudflap con GCC? Estos son controles de tiempo de ejecución, pero son útiles, ya que la mayoría de las veces tiene que ver con los índices calculados de tiempo de ejecución. En lugar de continuar trabajando silenciosamente, le notificará sobre esos errores.

-fmudflap -fmudflapth -fmudflapir Para front-ends que lo soportan (C y C ++), instrumenta todas las operaciones arriesgadas de desreferenciación de apuntador / matriz, algunas funciones estándar de cadena / pila de biblioteca y algunas otras construcciones asociadas con pruebas de rango / validez. Los módulos así instrumentados deberían ser inmunes a los desbordamientos del búfer, al uso no válido del montón y a otras clases de errores de programación C / C ++. La instrumentación se basa en una biblioteca de tiempo de ejecución independiente (libmudflap), que se vinculará a un programa si -fmudflap se da en tiempo de enlace. El comportamiento en tiempo de ejecución del programa instrumentado está controlado por la variable de entorno MUDFLAP_OPTIONS. Consulte "env MUDFLAP_OPTIONS = -help a.out" para conocer sus opciones.

Utilice -fmudflapth en lugar de -fmudflap para compilar y vincular si su programa tiene varios subprocesos. Use -fmudflapir, además de -fmudflap o -fmudflapth, si la instrumentación debe ignorar las lecturas del puntero. Esto produce menos instrumentación (y, por lo tanto, una ejecución más rápida) y aún proporciona cierta protección contra la corrupción absoluta de las escrituras, pero permite que los datos se reproduzcan erróneamente dentro de un programa.

Esto es lo que me da el mudflap para tu ejemplo:

[js@HOST2 cpp]$ gcc -fstack-protector-all -fmudflap -lmudflap mudf.c [js@HOST2 cpp]$ ./a.out ******* mudflap violation 1 (check/write): time=1229801723.191441 ptr=0xbfdd9c04 size=56 pc=0xb7fb126d location=`mudf.c:4:3 (main)'' /usr/lib/libmudflap.so.0(__mf_check+0x3d) [0xb7fb126d] ./a.out(main+0xb9) [0x804887d] /usr/lib/libmudflap.so.0(__wrap_main+0x4f) [0xb7fb0a5f] Nearby object 1: checked region begins 0B into and ends 16B after mudflap object 0x8509cd8: name=`mudf.c:3:7 (main) a'' bounds=[0xbfdd9c04,0xbfdd9c2b] size=40 area=stack check=0r/3w liveness=3 alloc time=1229801723.191433 pc=0xb7fb09fd number of nearby objects: 1 [js@HOST2 cpp]$

Tiene un montón de opciones. Por ejemplo, puede desviar un proceso de gdb ante violaciones, puede mostrarle dónde se filtró su programa (usando -print-leaks ) o detectar lecturas de variables no inicializadas. Use MUDFLAP_OPTIONS=-help ./a.out para obtener una lista de opciones. Como mudflap solo genera direcciones y no nombres de archivo y líneas de la fuente, escribí un pequeño script de gawk:

/^ / { file = gensub(/([^(]*).*/, "//1", 1); addr = gensub(/.*/[([x[:xdigit:]]*)/]$/, "//1", 1); if(file && addr) { cmd = "addr2line -e " file " " addr cmd | getline laddr print $0 " (" laddr ")" close (cmd) next; } } 1 # print all other lines

Pipe la salida de mudflap en él, y se mostrará el archivo de origen y la línea de cada entrada backtrace.

También -fstack-protector[-all] :

-fstack-protector código adicional para comprobar desbordamientos de búfer, como ataques de destrucción de pila. Esto se hace agregando una variable de guardia a las funciones con objetos vulnerables. Esto incluye funciones que llaman alloca y funciones con búferes de más de 8 bytes. Las protecciones se inicializan cuando se ingresa una función y luego se verifican cuando la función sale. Si falla una verificación de guardia, se imprime un mensaje de error y el programa sale.

-fstack-protector-all Como -fstack-protector excepto que todas las funciones están protegidas.


-opción de comprobación de límites está disponible con gcc.

vale la pena leer este artículo http://www.doc.ic.ac.uk/~phjk/BoundsChecking.html

''le dorfier'' ha dado una respuesta adecuada a su pregunta, es su programa y es la forma en que se comporta C.


Creo que algunos compiladores lo hacen en ciertos casos. Por ejemplo, si mi memoria me sirve correctamente, los compiladores más nuevos de Microsoft tienen una opción de "Comprobación de seguridad del búfer" que detectará casos triviales de desbordamientos del búfer.

¿Por qué no todos los compiladores hacen esto? O bien (como se mencionó anteriormente) la representación interna utilizada por el compilador no se presta a este tipo de análisis estático o simplemente no es lo suficientemente alta de la lista de prioridades de los escritores. Para ser honesto, es una pena de cualquier manera.


Hay alguna extensión en gcc para eso (del lado del compilador) http://www.doc.ic.ac.uk/~awl03/projects/miro/

por otro lado, férula, rat y muchas otras herramientas de análisis de código estático lo habrían encontrado.

También puede usar valgrind en su código y ver la salida. http://valgrind.org/

otra biblioteca ampliamente utilizada parece ser la liberación

Es simplemente una decisión de diseño hecha. Lo que ahora lleva a esto.

Saludos Friedrich


Tienes razón, el comportamiento no está definido . Los punteros C99 deben señalar dentro o solo un elemento más allá de las estructuras de datos declaradas o asignadas en el montón.

Nunca he sido capaz de descubrir cómo las personas gcc deciden cuándo advertir. Me sorprendió saber que -Wall por sí mismo no advertirá sobre variables no inicializadas; como mínimo necesita -O , e incluso entonces la advertencia a veces se omite.

Conjeturo que debido a que las matrices ilimitadas son tan comunes en C, el compilador probablemente no tenga forma en sus árboles de expresión para representar una matriz que tenga un tamaño conocido en tiempo de compilación. Entonces, aunque la información está presente en la declaración, conjeturo que al usarla ya está perdida.

En segundo lugar la recomendación de valgrind . Si está programando en C, debe ejecutar valgrind en cada programa, todo el tiempo hasta que ya no pueda tomar el golpe de rendimiento.


La filosofía C es que el programador siempre tiene la razón . Por lo tanto, le permitirá acceder silenciosamente a cualquier dirección de memoria que brinde allí, asumiendo que siempre sabe lo que está haciendo y no le molestará con una advertencia.


La razón por la que C no lo hace es porque C no tiene la información. Una declaración como

int a[10];

hace dos cosas: asigna sizeof(int)*10 bytes de espacio (más, potencialmente, un poco de espacio muerto para la alineación), y pone una entrada en la tabla de símbolos que lee, conceptualmente,

a : address of a[0]

o en términos C

a : &a[0]

y eso es todo. De hecho, en C puede intercambiar *(a+i) con a[i] en (casi *) todos los casos sin efecto POR DEFINICIÓN. Entonces, su pregunta es equivalente a preguntar "¿por qué puedo agregar un número entero a este valor (de dirección)?"

* Prueba sorpresa: ¿cuál es el único caso en esto esto no es cierto?


GCC advierte sobre esto. Pero debes hacer dos cosas:

  1. Habilita la optimización Sin al menos -O2, GCC no está haciendo un análisis suficiente para saber qué es y que se escapó del límite.
  2. Cambie su ejemplo para que realmente se use a [], de lo contrario, GCC genera un programa no operativo y ha descartado por completo su asignación.

.

$ cat foo.c int main(void) { int a[10]; a[13] = 3; // oops, overwrote the return address return a[1]; } $ gcc -Wall -Wextra -O2 -c foo.c foo.c: In function ‘main’: foo.c:4: warning: array subscript is above array bounds

Por cierto: si devolviste [13] en tu programa de prueba, tampoco funcionaría, ya que GCC optimiza la matriz nuevamente.


No es una matriz estática.

Comportamiento no definido o no, está escribiendo en una dirección 13 enteros desde el comienzo de la matriz. ¿Cuál es tu responsabilidad? Existen varias técnicas C que intencionalmente mal asignan matrices por razones razonables. Y esta situación no es inusual en unidades de compilación incompletas.

Dependiendo de la configuración de su marcador, hay una serie de características de este programa que se marcarán, como el hecho de que nunca se utiliza la matriz. Y el compilador podría fácilmente optimizar su existencia y no decirte: un árbol cayendo en el bosque.

Es la forma C. Es tu matriz, tu memoria, haz lo que quieras con ella. :)

(Hay muchas herramientas de pelusa para ayudarlo a encontrar este tipo de cosas, y debe usarlas abundantemente. Aunque no todas funcionan a través del compilador, la compilación y el enlace a menudo son lo suficientemente tediosas).