Recientemente comencé a aprender C y estoy tomando una clase con C como asignatura. Actualmente estoy jugando con bucles y me encuentro con un comportamiento extraño que no sé cómo explicar.
#include <stdio.h>
int main()
{
int array[10],i;
for (i = 0; i <=10 ; i++)
{
array[i]=0; /*code should never terminate*/
printf("test \n");
}
printf("%d \n", sizeof(array)/sizeof(int));
return 0;
}
En mi computadora portátil con Ubuntu 14.04, este código no se rompe. Se ejecuta hasta su finalización. En la computadora de mi escuela que ejecuta CentOS 6.6, también funciona bien. En Windows 8.1, el ciclo nunca termina.
Lo que es aún más extraño es que cuando edito la condición del for
bucle en:, i <= 11
el código solo termina en mi computadora portátil con Ubuntu. Nunca termina en CentOS y Windows.
¿Alguien puede explicar lo que está sucediendo en la memoria y por qué los diferentes sistemas operativos que ejecutan el mismo código dan resultados diferentes?
EDITAR: Sé que el bucle for sale de los límites. Lo estoy haciendo intencionalmente. Simplemente no puedo entender cómo el comportamiento puede ser diferente en diferentes sistemas operativos y computadoras.
i
se almacena justo después del finalarray
y la está sobrescribiendoarray[10]=0;
. Este podría no ser el caso en una compilación optimizada en la misma plataforma, que puede almacenarsei
en un registro y nunca referirse a ella en la memoria.Respuestas:
Acabas de descubrir la memoria pisando fuerte. Puede leer más sobre esto aquí: ¿Qué es un "pisoteo de memoria"?
Cuando asigna
int array[10],i;
, esas variables van a la memoria (específicamente, se asignan en la pila, que es un bloque de memoria asociado con la función).array[]
yi
son probablemente adyacentes entre sí en la memoria. Parece que en Windows 8.1,i
se encuentra enarray[10]
. En CentOS,i
se encuentra enarray[11]
. Y en Ubuntu, no está en ningún lugar (¿tal vez está enarray[-1]
?).Intente agregar estas declaraciones de depuración a su código. Debe notar que en la iteración 10 u 11,
array[i]
apunta ai
.fuente
array[10]
destruir el marco de la pila, por ejemplo. ¿Cómo puede haber una diferencia entre el código con o sin la salida de depuración? Sii
nunca se necesita la dirección de , el compilador puede optimizari
. en un registro, cambiando así el diseño de la memoria en la pila ...array[10]=0
. Si compilara su código con la optimización activada, esto probablemente no sucedería. reglas de alias que limitan los tipos de acceso a la memoria que se debe suponer que potencialmente se superponen a otra memoria. Como variable local de la que nunca tomas la dirección, creo que un compilador debería ser capaz de asumir que nada lo alias. De todos modos, descartar el final de una matriz es un comportamiento indefinido. Siempre trate de evitarlo dependiendo de eso.El error se encuentra entre estos fragmentos de código:
Como
array
solo tiene 10 elementos, en la última iteraciónarray[10] = 0;
hay un desbordamiento de búfer. Los desbordamientos de la memoria intermedia son COMPORTAMIENTO INDEFINIDO , lo que significa que pueden formatear su disco duro o hacer que los demonios salgan volando de su nariz.Es bastante común que todas las variables de pila se distribuyan adyacentes entre sí. Si
i
se encuentra enarray[10]
las escrituras a, a continuación, la UB se restablecerái
a0
, lo que conduce al bucle sin terminar.Para solucionarlo, cambie la condición del bucle a
i < 10
.fuente
rm -rf /
incluso cuando no eres root, no "formateando" todo el disco, por supuesto, pero aún destruyendo todos sus datos. Ay.En lo que debería ser la última ejecución del ciclo, debe escribir
array[10]
, pero solo hay 10 elementos en la matriz, numerados del 0 al 9. La especificación del lenguaje C dice que este es un "comportamiento indefinido". Lo que esto significa en la práctica es que su programa intentará escribir en laint
pieza de memoria del tamaño que se encuentra inmediatamente despuésarray
en la memoria. Lo que sucede entonces depende de lo que, de hecho, se encuentra allí, y esto depende no solo del sistema operativo sino también del compilador, de las opciones del compilador (como la configuración de optimización), de la arquitectura del procesador, del código circundante , etc. Incluso podría variar de una ejecución a otra, por ejemplo, debido a la aleatorización del espacio de direcciones (probablemente no en este ejemplo de juguete, pero sucede en la vida real). Algunas posibilidades incluyen:i
. El ciclo nunca termina porque sei
reinicia en 0.array
está justo al final de una página de memoria virtual y la página siguiente no está asignada.Lo que observó en Windows fue que el compilador decidió colocar la variable
i
inmediatamente después de la matriz en la memoria, por lo quearray[10] = 0
terminó asignando ai
. En Ubuntu y CentOS, el compilador no se ubicói
allí. Casi todas las implementaciones de C agrupan variables locales en la memoria, en una pila de memoria , con una excepción importante: algunas variables locales se pueden colocar completamente en registros . Incluso si la variable está en la pila, el compilador determina el orden de las variables, y puede depender no solo del orden en el archivo fuente sino también de sus tipos (para evitar desperdiciar memoria en restricciones de alineación que dejarían agujeros) , en sus nombres, en algún valor hash utilizado en la estructura de datos interna de un compilador, etc.Si desea averiguar qué decidió hacer su compilador, puede pedirle que le muestre el código del ensamblador. Ah, y aprende a descifrar ensamblador (es más fácil que escribirlo). Con GCC (y algunos otros compiladores, especialmente en el mundo Unix), pase la opción
-S
de producir código ensamblador en lugar de un binario. Por ejemplo, aquí está el fragmento de ensamblador para que el bucle se compile con GCC en amd64 con la opción de optimización-O0
(sin optimización), con comentarios agregados manualmente:Aquí la variable
i
está 52 bytes debajo de la parte superior de la pila, mientras que la matriz comienza 48 bytes debajo de la parte superior de la pila. Por lo tanto, este compilador se colocai
justo antes de la matriz; sobrescribiríai
si le escribieraarray[-1]
. Si cambiaarray[i]=0
aarray[9-i]=0
, obtendrá un bucle infinito en esta plataforma particular con estas opciones de compilador particulares.Ahora compilemos su programa con
gcc -O1
.Eso es más corto! El compilador no solo se ha negado a asignar una ubicación de pila
i
, solo se almacena en el registroebx
, sino que no se ha molestado en asignar memoriaarray
o generar código para configurar sus elementos, porque notó que ninguno de los elementos son utilizados alguna vezPara que este ejemplo sea más revelador, asegurémonos de que las asignaciones de la matriz se realicen proporcionando al compilador algo que no puede optimizar. Una forma sencilla de hacerlo es utilizar la matriz de otro archivo - a causa de compilación separada, el compilador no sabe lo que pasa en otro archivo (a menos que se optimiza en tiempo de enlace, que
gcc -O0
ogcc -O1
no lo hace). Crear un archivo fuente queuse_array.c
contengay cambia tu código fuente a
Compilar con
Esta vez el código del ensamblador se ve así:
Ahora la matriz está en la pila, 44 bytes desde la parte superior. ¿Qué hay de
i
? ¡No aparece en ningún lado! Pero el contador de bucle se mantiene en el registrorbx
. No es exactamentei
, pero la dirección de laarray[i]
. El compilador ha decidido que, dado que el valor dei
nunca se usó directamente, no tenía sentido realizar operaciones aritméticas para calcular dónde almacenar 0 durante cada ejecución del ciclo. En cambio, esa dirección es la variable de bucle, y la aritmética para determinar los límites se realizó en parte en tiempo de compilación (multiplique 11 iteraciones por 4 bytes por elemento de matriz para obtener 44) y en parte en tiempo de ejecución, pero de una vez por todas antes de que comience el bucle ( realizar una resta para obtener el valor inicial).Incluso en este ejemplo muy simple, hemos visto cómo cambiar las opciones del compilador (activar la optimización) o cambiar algo menor (
array[i]
aarray[9-i]
) o incluso cambiar algo aparentemente no relacionado (agregar la llamada ause_array
) puede marcar una diferencia significativa en lo que generó el programa ejecutable por el compilador lo hace. Las optimizaciones del compilador pueden hacer muchas cosas que pueden parecer poco intuitivas en los programas que invocan un comportamiento indefinido . Es por eso que el comportamiento indefinido se deja completamente indefinido. Cuando se desvía ligeramente de las pistas, en los programas del mundo real, puede ser muy difícil entender la relación entre lo que hace el código y lo que debería haber hecho, incluso para programadores experimentados.fuente
A diferencia de Java, C no verifica los límites de la matriz, es decir, no existe
ArrayIndexOutOfBoundsException
, el trabajo de asegurarse de que el índice de la matriz sea válido se deja al programador. Hacer esto a propósito conduce a un comportamiento indefinido, cualquier cosa podría suceder.Para una matriz:
los índices solo son válidos en el rango
0
de9
. Sin embargo, estás intentando:acceda
array[10]
aquí, cambie la condición ai < 10
fuente
Tiene una violación de límites, y en las plataformas que no terminan, creo que está configurando inadvertidamente
i
a cero al final del ciclo, para que comience nuevamente.array[10]
no es válido; contiene 10 elementos, aarray[0]
travésarray[9]
, yarray[10]
es el undécimo. Su ciclo debe escribirse para detenerse antes10
, de la siguiente manera:Donde
array[10]
land está definido por la implementación, y de manera divertida, en dos de sus plataformas, aterriza eni
, que esas plataformas aparentemente presentan directamente despuésarray
.i
se establece en cero y el ciclo continúa para siempre. Para sus otras plataformas,i
puede estar ubicado antesarray
oarray
puede tener algo de relleno después.fuente
Usted declara que los
int array[10]
mediosarray
tienen un índice0
de9
(10
elementos enteros totales que puede contener). Pero el siguiente bucle,pasará
0
a10
significa11
tiempo. Pori = 10
lo tanto, cuándo desbordará el búfer y causará un comportamiento indefinido .Intenta esto:
o,
fuente
No está definido en
array[10]
, y proporciona un comportamiento indefinido como se describió anteriormente. Piénsalo así:Tengo 10 artículos en mi carrito de compras. Son:
0: Una caja de cereal
1: Pan
2: Leche
3: Pastel
4: Huevos
5: Pastel
6: A 2 litros de refresco
7: Ensalada
8: Hamburguesas
9: Helado
cart[10]
no está definido y puede dar una excepción fuera de límites en algunos compiladores. Pero, muchos aparentemente no lo hacen. El undécimo elemento aparente es un elemento que no está realmente en el carrito. El undécimo elemento apunta a lo que voy a llamar un "elemento poltergeist". Nunca existió, pero estaba allí.¿Por qué algunos compiladores dan
i
un índice dearray[10]
oarray[11]
inclusoarray[-1]
es debido a su declaración de inicialización / declaración? Algunos compiladores interpretan esto como:int
s paraarray[10]
y otroint
bloque. Para que sea más fácil, colóquelos uno al lado del otro".array[10]
no apuntei
.i
enarray[-1]
(porque un índice de una matriz no puede, o no debería, ser negativo), o asignarlo en un lugar completamente diferente porque el sistema operativo puede manejarlo, y es más seguro.Algunos compiladores quieren que las cosas vayan más rápido, y algunos compiladores prefieren la seguridad. Se trata del contexto. Si estaba desarrollando una aplicación para el antiguo sistema operativo BREW (el sistema operativo de un teléfono básico), por ejemplo, no me importaría la seguridad. Si estaba desarrollando para un iPhone 6, podría funcionar rápido sin importar qué, por lo que necesitaría un énfasis en la seguridad. (En serio, ¿has leído las pautas de la tienda de aplicaciones de Apple o has leído sobre el desarrollo de Swift y Swift 2.0?)
fuente
Como creó una matriz de tamaño 10, la condición del bucle debe ser la siguiente:
Actualmente está intentando acceder a la ubicación no asignada desde la memoria utilizando
array[10]
y está causando un comportamiento indefinido . Comportamiento indefinido significa que su programa se comportará de manera indeterminada, por lo que puede dar diferentes resultados en cada ejecución.fuente
Bueno, el compilador de C tradicionalmente no verifica los límites. Puede obtener un error de segmentación en caso de que se refiera a una ubicación que no "pertenezca" a su proceso. Sin embargo, las variables locales se asignan en la pila y, dependiendo de la forma en que se asigne la memoria, el área justo más allá de la matriz (
array[10]
) puede pertenecer al segmento de memoria del proceso. Por lo tanto, no se lanza una trampa de falla de segmentación y eso es lo que parece experimentar. Como otros han señalado, este es un comportamiento indefinido en C y su código puede considerarse errático. Como estás aprendiendo C, es mejor que te acostumbres a comprobar los límites de tu código.fuente
Más allá de la posibilidad de que la memoria pueda establecerse de modo que un intento de escribir
a[10]
realmente sobrescribai
, también sería posible que un compilador de optimización pueda determinar que la prueba de bucle no puede alcanzarse con un valori
mayor que diez sin que el código haya accedido primero al elemento de matriz inexistentea[10]
.Dado que un intento de acceder a ese elemento sería un comportamiento indefinido, el compilador no tendría obligaciones con respecto a lo que el programa podría hacer después de ese punto. Más específicamente, dado que el compilador no tendría la obligación de generar código para verificar el índice de bucle en cualquier caso en que pudiera ser mayor que diez, no tendría la obligación de generar código para verificarlo en absoluto; En su lugar, podría suponer que la
<=10
prueba siempre dará verdadero. Tenga en cuenta que esto sería cierto incluso si el código lo leería ena[10]
lugar de escribirlo.fuente
Cuando itera más allá
i==9
, asigna cero a los 'elementos de la matriz' que realmente se encuentran más allá de la matriz , por lo que está sobrescribiendo algunos otros datos. Lo más probable es que sobrescriba lai
variable, que se encuentra despuésa[]
. De esa manera, simplemente restablece lai
variable a cero y, por lo tanto, reinicia el ciclo.Puede descubrirlo usted mismo si imprime
i
en el bucle:en lugar de solo
Por supuesto, ese resultado depende en gran medida de la asignación de memoria para sus variables, que a su vez depende de un compilador y su configuración, por lo que generalmente es un comportamiento indefinido ; es por eso que los resultados en diferentes máquinas o diferentes sistemas operativos o en diferentes compiladores pueden diferir.
fuente
el error está en la matriz de porciones [10] w / c también es la dirección de i (int array [10], i;). cuando la matriz [10] se establece en 0, entonces i sería 0 w / c restablece todo el ciclo y provoca el ciclo infinito. habrá un ciclo infinito si la matriz [10] está entre 0-10. el ciclo correcto debe ser para (i = 0; i <10; i ++) {...} int matriz [10], i; para (i = 0; i <= 10; i ++) matriz [i] = 0;
fuente
Sugeriré algo que no encontraré arriba:
Intente asignar la matriz [i] = 20;
Supongo que esto debería terminar el código en todas partes ... (dado que mantienes i <= 10 o ll)
Si esto funciona, puede decidir firmemente que las respuestas especificadas aquí ya son correctas [la respuesta relacionada con la memoria pisando fuerte por ejemplo].
fuente
Hay dos cosas mal aquí. El int i es en realidad un elemento de matriz, matriz [10], como se ve en la pila. Debido a que ha permitido que la indexación haga realmente array [10] = 0, el índice de bucle, i, nunca excederá de 10. Hazlo
for(i=0; i<10; i+=1)
.i ++ es, como K&R lo llamaría, 'mal estilo'. Está incrementando i por el tamaño de i, no 1. i ++ es para matemáticas de puntero e i + = 1 es para álgebra. Si bien esto depende del compilador, no es una buena convención para la portabilidad.
fuente
i
NO es un elemento de la matriza[10]
, no hay obligación ni sugerencia para que un compilador la coloque en la pila inmediatamente despuésa[]
; también puede ubicarse antes de la matriz o separarse con algo de espacio adicional. Incluso podría asignarse fuera de la memoria principal, por ejemplo en un registro de CPU. Tampoco es cierto que++
sea para punteros y no para enteros. Completamente incorrecto es 'i ++ está incrementando i por el tamaño de i' - ¡lea la descripción del operador en la definición del lenguaje!i
: es deint
tipo. Es un número entero , no un puntero; un número entero, usado como índice para elarray
,.