Loop aparentemente infinito termina, a menos que System.out.println seja usado


91

Eu tinha um código simples que deveria ser um loop infinito, pois xsempre estará crescendo e sempre permanecerá maior que j.

int x = 5;
int y = 9;
for (int j = 0; j < x; j++) {
   x = x + y;
}
System.out.println(y);

mas como está, ele imprime ye não faz loop indefinidamente. Eu não consigo descobrir o porquê. No entanto, quando ajusto o código da seguinte maneira:

int x = 5;
int y = 9;
for (int j = 0; j < x; j++) {
    x = x + y;
    System.out.println(y);
}
System.out.println(y);

Torna-se um ciclo sem fim e não tenho ideia do porquê. O java reconhece que é um loop infinito e o ignora na primeira situação, mas precisa executar uma chamada de método na segunda para que se comporte conforme o esperado? Confuso :)


4
O segundo loop é infinito porque o limite superior xcresce mais rápido do que a variável do loop j. Em outras palavras, jnunca atingirá um limite superior, portanto, o loop será executado "para sempre". Bem, não para sempre, você provavelmente terá um estouro em algum ponto.
Tim Biegeleisen 01 de

75
Não é um loop infinito, apenas leva 238609294 vezes para sair do loop for no primeiro caso e, na segunda vez, imprime o valor de y238609294 vezes
N00b Pr0grammer

13
resposta de uma palavra: estouro
qwr

20
Curiosamente, em System.out.println(x)vez de yno final, teria mostrado instantaneamente qual era o problema
JollyJoker

9
@TeroLahtinen não, não seria. Leia as especificações da linguagem Java se tiver dúvidas sobre o tipo int. É independente de hardware.
9ilsdx 9rvj 0lo 01 de

Respostas:


161

Ambos os exemplos não são infinitos.

O problema é a limitação do inttipo em Java (ou praticamente qualquer outra linguagem comum). Quando o valor de xatingir 0x7fffffff, adicionar qualquer valor positivo resultará em estouro e o xtorna - se negativo, portanto menor que j.

A diferença entre o primeiro e o segundo loop é que o código interno leva muito mais tempo e provavelmente levaria vários minutos até xestourar. Para o primeiro exemplo, pode levar menos de um segundo ou muito provavelmente o código será removido pelo otimizador, pois não tem nenhum efeito.

Conforme mencionado na discussão, o tempo dependerá muito de como o SO armazena a saída, se ela é enviada para o emulador de terminal etc., portanto, pode ser muito maior do que alguns minutos.


48
Acabei de experimentar um programa (no meu laptop) que imprime uma linha em um loop. Eu cronometrei e ele foi capaz de imprimir aproximadamente 1000 linhas / segundo. Com base no comentário do N00b de que o loop será executado 238609294 vezes, levará cerca de 23861 segundos para o loop terminar - mais de 6,6 horas. Um pouco mais do que "vários minutos".
ajb 01 de

11
@ajb: Depende da implementação. O IIRC println()no Windows é uma operação de bloqueio, enquanto em (alguns?) Unix ele é armazenado em buffer e é muito mais rápido. Também tente usar print(), que buffers até atingir um \n(ou o buffer se encher ou flush()ser chamado)
BlueRaja - Danny Pflughoeft

6
Também depende do terminal mostrando a saída. Consulte stackoverflow.com/a/21947627/53897 para um exemplo extremo (onde a desaceleração foi devido à quebra de linha)
Thorbjørn Ravn Andersen

1
Sim, é armazenado em buffer no UNIX, mas ainda está bloqueando. Assim que o buffer de 8K ou mais for preenchido, ele bloqueará até que haja espaço. A velocidade dependerá muito da rapidez com que é consumida. Redirecionar a saída para / dev / null será o mais rápido, mas enviá-la para o terminal, o padrão, exigirá atualizações de gráficos na tela e muito mais poder de computação, pois renderiza as fontes, tornando-as mais lentas.
penguin359 01 de

2
@Zbynek oh, provavelmente sim, mas isso me lembra, os terminais de E / S normalmente serão armazenados em buffer de linha, e não em bloco, então provavelmente cada println resultará em uma chamada de sistema, tornando mais lenta a caixa do terminal.
penguin359

33

Uma vez que eles são declarados como int, assim que atingir o valor máximo, o loop será interrompido e o valor x se tornará negativo.

Mas quando System.out.println é adicionado ao loop, a velocidade de execução se torna visível (já que a saída para o console diminuirá a velocidade de execução). No entanto, se você deixar o segundo programa (aquele com syso dentro do loop) funcionar por tempo suficiente, ele deverá ter o mesmo comportamento do primeiro (aquele sem syso dentro do loop).


21
As pessoas não percebem quanto spam para o console pode tornar seu código mais lento.
user9993 01 de

13

Pode haver dois motivos para isso:

  1. Java otimiza o forloop e, como não há uso de xafter the loop, simplesmente remove o loop. Você pode verificar isso colocando System.out.println(x);instrução após o loop.

  2. Pode ser possível que o Java não esteja realmente otimizando o loop e esteja executando o programa corretamente e, eventualmente, xficará grande demais para intestourar. O estouro de inteiro provavelmente tornará o inteiro xnegativo, o que será menor que j e, portanto, sairá do loop e imprimirá o valor de y. Isso também pode ser verificado adicionando System.out.println(x);após o loop.

Além disso, mesmo no primeiro caso, eventualmente, o estouro ocorrerá, tornando-o no segundo caso, de forma que nunca será um verdadeiro loop infinito.


14
Eu escolho a porta número 2.
Robby Cornelissen

Verdade. Ele entrou na escala negativa e saiu do loop. Mas a sysouté tão lento para adicionar a ilusão de um loop infinito.
Pavan Kumar

4
1. Seria um bug. As otimizações do compilador não podem alterar o comportamento de um programa. Se for um loop infinito, o compilador pode otimizar tudo o que deseja; no entanto, o resultado ainda deve ser um loop infinito. A solução real é que o OP está errado: nenhum dos dois é um loop infinito, um apenas faz mais trabalho do que o outro, por isso leva mais tempo.
Jörg W Mittag

1
@ JörgWMittag Neste caso, x é uma variável local sem relação com qualquer outra coisa. Como tal, é possível que seja totalmente otimizado. Mas deve-se olhar o bytecode para determinar se é esse o caso, nunca apenas assumir que o compilador fez algo assim.
Esperançosamente útil

1

Ambos não são loops infinitos, inicialmente j = 0, enquanto j <x, j aumenta (j ++), ej é um número inteiro, então o loop seria executado até atingir o valor máximo e então estouraria (Um estouro de inteiro é a condição que ocorre quando o resultado de uma operação aritmética, como multiplicação ou adição, excede o tamanho máximo do tipo inteiro usado para armazená-lo.). para o segundo exemplo, o sistema apenas imprime o valor de y até que o loop seja interrompido.

se você estiver procurando por um exemplo de loop infinito, deve ser parecido com este

int x = 6;

for (int i = 0; x < 10; i++) {
System.out.println("Still Looping");
}

porque (x) nunca alcançaria o valor de 10;

você também pode criar um loop infinito com um loop for duplo:

int i ;

  for (i = 0; i <= 10; i++) {
      for (i = 0; i <= 5; i++){
         System.out.println("Repeat");   
      }
 }

este laço é infinito porque o primeiro laço for diz i <10, o que é verdade, então ele vai para o segundo laço for e o segundo laço for aumenta o valor de (i) até que seja == 5. Então ele prossegue para o primeiro loop for novamente porque i <10, o processo continua se repetindo porque ele reinicia após o segundo loop for


1

É um loop finito porque uma vez que o valor de xexcede 2,147,483,647(que é o valor máximo de an int), xse tornará negativo e não maior do que jqualquer mais, quer você imprima y ou não.

Você pode apenas alterar o valor de ypara 100000e imprimir yno loop e o loop será interrompido muito em breve.

A razão pela qual você acha que ele se tornou infinito é que ele System.out.println(y);fez o código ser executado muito mais lento do que sem nenhuma ação.


0

Problema interessante Na verdade, em ambos os casos, o loop não é infinito

Mas a principal diferença entre eles é quando terminará e quanto tempo xlevará para exceder o intvalor máximo que é2,147,483,647 depois disso atingirá o estado de estouro e o loop terminará.

A melhor maneira de entender esse problema é testar um exemplo simples e preservar seus resultados.

Exemplo :

for(int i = 10; i > 0; i++) {}
System.out.println("finished!");

Resultado:

finished!
BUILD SUCCESSFUL (total time: 0 seconds)

Depois de testar este loop infinito, levará menos de 1 segundo para terminar.

for(int i = 10; i > 0; i++) {
    System.out.println("infinite: " + i);
}
System.out.println("finished!");

Resultado:

infinite: 314572809
infinite: 314572810
infinite: 314572811
.
.
.
infinite: 2147483644
infinite: 2147483645
infinite: 2147483646
infinite: 2147483647
finished!
BUILD SUCCESSFUL (total time: 486 minutes 25 seconds)

Neste caso de teste, você notará uma grande diferença no tempo necessário para encerrar e concluir a execução do programa.

Se você não tiver paciência, pensará que esse loop é interminável e não terminará, mas na verdade levará horas para terminar e atingir o estado de estouro no ivalor.

Finalmente concluímos, depois de colocar a instrução print dentro do loop for, que levará muito mais tempo do que o loop no primeiro caso sem a instrução print.

O tempo necessário para executar o programa depende das especificações do seu computador, em particular da capacidade de processamento (capacidade do processador), do sistema operacional e do IDE que está compilando o programa.

Eu testo este caso em:

Lenovo 2.7 GHz Intel Core i5

SO: Windows 8.1 64x

IDE: NetBeans 8.2

Demora cerca de 8 horas (486 minutos) para terminar o programa.

Além disso, você pode notar que o incremento de passo no loop for i = i + 1 for é um fator muito lento para atingir o valor máximo int.

Podemos alterar esse fator e tornar o incremento de etapa mais rápido para testar o loop em menos tempo.

se colocarmos i = i * 10e testarmos:

for(int i = 10; i > 0; i*=10) {
           System.out.println("infinite: " + i);
}
     System.out.println("finished!");

Resultado:

infinite: 100000
infinite: 1000000
infinite: 10000000
infinite: 100000000
infinite: 1000000000
infinite: 1410065408
infinite: 1215752192
finished!
BUILD SUCCESSFUL (total time: 0 seconds)

Como você pode ver, é muito rápido em comparação com o loop anterior

leva menos de 1 segundo para encerrar e concluir a execução do programa.

Após este exemplo de teste, acho que deve esclarecer o problema e provar a validade de Zbynek Vyskovsky - a resposta do kvr000 , também será a resposta a esta pergunta .

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.