c - guia - qgis manual
¿Por qué esto para el bucle sale en algunas plataformas y no en otras? (14)
En mi computadora portátil con Ubuntu 14.04, este código no se rompe y 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 más extraño es cuando edito el condicional del bucle
for
para:i <= 11
, el código solo termina en mi computadora portátil con Ubuntu. CentOS y Windows nunca terminan.
Acabas de descubrir la memoria pisando fuerte. Puede leer más sobre esto aquí: ¿Qué es un "pisotón 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[]
y
i
probablemente estén adyacentes entre sí en la memoria.
Parece que en Windows 8.1,
i
ubicado en la
array[10]
.
En CentOS,
i
se encuentra en la
array[11]
.
Y en Ubuntu, no está en ningún lugar (¿tal vez está en la
array[-1]
?).
Intente agregar estas declaraciones de depuración a su código.
Debería notar que en la iteración 10 u 11, la
array[i]
apunta a
i
.
#include <stdio.h>
int main()
{
int array[10],i;
printf ("array: %p, &i: %p/n", array, &i);
printf ("i is offset %d from array/n", &i - array);
for (i = 0; i <=11 ; i++)
{
printf ("%d: Writing 0 to address %p/n", i, &array[i]);
array[i]=0; /*code should never terminate*/
}
return 0;
}
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 bucle
for
para:
i <= 11
, el código solo termina en mi computadora portátil con Ubuntu.
Nunca termina en CentOS y Windows.
¿Alguien puede explicar qué 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.
A diferencia de Java, C no verifica los límites de la matriz, es decir, no hay
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:
int array[10]
los índices solo son válidos en el rango de
0
a
9
.
Sin embargo, estás tratando de:
for (i = 0; i <=10 ; i++)
Acceda a la
array[10]
aquí, cambie la condición a
i < 10
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 "pertenece" a su proceso.
Sin embargo, las variables locales se asignan en la pila y, dependiendo de la forma en que se asigna la memoria, el área que se encuentra 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.
Como creó una matriz de tamaño 10, la condición del bucle debe ser la siguiente:
int array[10],i;
for (i = 0; i <10 ; i++)
{
Actualmente está intentando acceder a la ubicación no asignada desde la memoria utilizando la
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.
Cuando itera más allá de
i==9
, asigna cero a los ''elementos de la matriz'' que realmente se encuentran más
allá de la matriz
, por lo que sobrescribe algunos otros datos.
Lo más probable es que sobrescriba la variable
i
, que se encuentra después de
a[]
.
De esa manera, simplemente
restablece la variable
i
a cero
y, por lo tanto, reinicia el ciclo.
Podría descubrirlo usted mismo si imprimió
i
en el bucle:
printf("test i=%d/n", i);
en lugar de solo
printf("test /n");
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.
Declaras
int array[10]
significa que la
array
tiene un índice de
0
a
9
(total de
10
elementos enteros que puede contener).
Pero el siguiente ciclo,
for (i = 0; i <=10 ; i++)
repetirá de
0
a
10
significa
11
veces.
Por lo tanto, cuando
i = 10
desbordará el búfer y causará un
comportamiento indefinido
.
Así que prueba esto:
for (i = 0; i < 10 ; i++)
o,
for (i = 0; i <= 9 ; i++)
El error se encuentra entre estos fragmentos de código:
int array[10],i;
for (i = 0; i <=10 ; i++)
array[i]=0;
Como la
array
solo tiene 10 elementos, en la última iteración, la
array[10] = 0;
Es 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
está ubicado donde escribe
array[10]
, entonces el UB restablecerá
i
a
0
, lo que lleva al bucle no terminado.
Para solucionarlo, cambie la condición del bucle a
i < 10
.
En lo que debería ser la última ejecución del ciclo, usted escribe en la
array[10]
, pero solo hay 10 elementos en la matriz, numerados del 0 al 9. La especificación del lenguaje C dice que esto es "comportamiento indefinido".
Lo que esto significa en la práctica es que su programa intentará escribir en la pieza de memoria de tamaño interno que se encuentra inmediatamente después de la
array
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:
- La ubicación no fue utilizada. El ciclo termina normalmente.
- La ubicación se usó para algo que resultó tener el valor 0. El ciclo termina normalmente.
- La ubicación contenía la dirección de retorno de la función. El ciclo termina normalmente, pero luego el programa se bloquea porque intenta saltar a la dirección 0.
-
La ubicación contiene la variable
i
. El ciclo nunca termina porque reinicio en 0. - La ubicación contiene alguna otra variable. El ciclo termina normalmente, pero luego suceden cosas "interesantes".
-
La ubicación es una dirección de memoria no válida, por ejemplo, porque la
array
está justo al final de una página de memoria virtual y la página siguiente no está asignada. - catb.org/jargon/html/N/nasal-demons.html . Afortunadamente, la mayoría de las computadoras carecen del hardware necesario.
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 que la
array[10] = 0
terminó asignándose a
i
.
En Ubuntu y CentOS, el compilador no
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
registers
.
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
para 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:
.L3:
movl -52(%rbp), %eax ; load i to register eax
cltq
movl $0, -48(%rbp,%rax,4) ; set array[i] to 0
movl $.LC0, %edi
call puts ; printf of a constant string was optimized to puts
addl $1, -52(%rbp) ; add 1 to i
.L2:
cmpl $10, -52(%rbp) ; compare i to 10
jle .L3
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.
Entonces, este compilador ha colocado
i
justo antes de la matriz;
sobrescribiría
i
si por casualidad escribiera en la
array[-1]
.
Si cambia
array[i]=0
a
array[9-i]=0
, obtendrá un bucle infinito en esta plataforma particular con estas opciones de compilador particulares.
Ahora
gcc -O1
su programa con
gcc -O1
.
movl $11, %ebx
.L3:
movl $.LC0, %edi
call puts
subl $1, %ebx
jne .L3
Eso es más corto!
El compilador no solo se ha negado a asignar una ubicación de pila para
i
, solo se ha almacenado en el registro
ebx
, sino que no se ha molestado en asignar memoria para la
array
o en generar código para configurar sus elementos, porque notó que ninguno de los elementos se usan alguna vez.
Para 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 manera fácil de hacerlo es usar la matriz de otro archivo: debido a una compilación separada, el compilador no sabe qué sucede en otro archivo (a menos que se optimice en el momento del enlace, que
gcc -O0
o
gcc -O1
no )
Cree un archivo fuente
use_array.c
contenga
void use_array(int *array) {}
y cambia tu código fuente a
#include <stdio.h>
void use_array(int *array);
int main()
{
int array[10],i;
for (i = 0; i <=10 ; i++)
{
array[i]=0; /*code should never terminate*/
printf("test /n");
}
printf("%zd /n", sizeof(array)/sizeof(int));
use_array(array);
return 0;
}
Compilar con
gcc -c use_array.c
gcc -O1 -S -o with_use_array1.c with_use_array.c use_array.o
Esta vez el código del ensamblador se ve así:
movq %rsp, %rbx
leaq 44(%rsp), %rbp
.L3:
movl $0, (%rbx)
movl $.LC0, %edi
call puts
addq $4, %rbx
cmpq %rbp, %rbx
jne .L3
Ahora la matriz está en la pila, 44 bytes desde la parte superior.
¿Y
i
?
¡No aparece en ningún lado!
Pero el contador de bucle se mantiene en el registro
rbx
.
No es exactamente
i
, sino la dirección de la
array[i]
.
El compilador ha decidido que, dado que el valor de
i
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 del 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]
a
array[9-i]
) o incluso cambiar algo aparentemente no relacionado (agregando la llamada a
use_array
) puede hacer una diferencia significativa en lo que hace el programa ejecutable generado por el compilador.
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 comprender la relación entre lo que hace el código y lo que debería haber hecho, incluso para programadores experimentados.
Está indefinido en la
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: 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.
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 un índice de
array[10]
o
array[11]
o incluso
array[-1]
es debido a su declaración de inicialización / declaración?
Algunos compiladores interpretan esto como:
-
"Asigne 10 bloques de
int
s para laarray[10]
y otro bloqueint
. Para que sea más fácil, colóquelos uno al lado del otro". -
Igual que antes, pero muévalo uno o dos espacios, de modo que la
array[10]
no apunte ai
. -
Haga lo mismo que antes, pero asigne
i
en laarray[-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 un iPhone 6, entonces 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?)
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 realmente haga la matriz [10] = 0, el índice de bucle, i, nunca excederá 10. Haga que sea
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.
Más allá de la posibilidad de que la memoria pueda establecerse de modo que un intento de escribir en
a[10]
realmente sobrescriba
i
, también sería posible que un compilador optimizador pueda determinar que la prueba de bucle no puede alcanzarse con un valor de
i
mayor que diez sin que el código haya accedido primero al elemento de matriz inexistente
a[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 prueba
<=10
siempre dará verdadero.
Tenga en cuenta que esto sería cierto incluso si el código leería
a[10]
lugar de escribirlo.
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].
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,
array[0]
a
array[9]
, y
array[10]
es el 11º.
Su ciclo debe escribirse para detenerse
antes de las
10
, de la siguiente manera:
for (i = 0; i < 10; i++)
Donde
array[10]
aterriza está definido por la implementación, y de manera divertida, en dos de sus plataformas, aterriza en
i
, que esas plataformas aparentemente se colocan directamente después de
array
.
i
se establece en cero y el ciclo continúa para siempre.
Para sus otras plataformas,
i
posible que esté ubicado antes de la
array
, o la
array
puede tener algo de relleno después.
el error está en la porción array [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;