Comecei recentemente a aprender C e estou tendo uma aula com C como disciplina. Atualmente, estou brincando com loops e estou tendo um comportamento estranho que não sei 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;
}
No meu laptop executando o Ubuntu 14.04, esse código não quebra. É executado até a conclusão. No computador da minha escola executando o CentOS 6.6, ele também funciona bem. No Windows 8.1, o loop nunca termina.
O que é ainda mais estranho é que, quando edito a condição do for
loop para:, i <= 11
o código só termina no meu laptop executando o Ubuntu. Ele nunca termina no CentOS e no Windows.
Alguém pode explicar o que está acontecendo na memória e por que os diferentes sistemas operacionais executando o mesmo código fornecem resultados diferentes?
EDIT: Eu sei que o loop for sai dos limites. Eu estou fazendo isso intencionalmente. Eu simplesmente não consigo descobrir como o comportamento pode ser diferente em diferentes sistemas operacionais e computadores.
i
é armazenada logo após o final dearray
e você a substituiarray[10]=0;
. Pode não ser o caso de uma construção otimizada na mesma plataforma, que pode armazenari
em um registro e nunca se referir a ele na memória.Respostas:
Você acabou de descobrir a falta de memória. Você pode ler mais sobre isso aqui: O que é um “stomp de memória”?
Quando você aloca
int array[10],i;
, essas variáveis são armazenadas na memória (especificamente, elas são alocadas na pilha, que é um bloco de memória associado à função).array[]
ei
provavelmente estão adjacentes um ao outro na memória. Parece que no Windows 8.1,i
está localizado emarray[10]
. No CentOS,i
está localizado emarray[11]
. E no Ubuntu, ele não está em nenhum lugar (talvez estejaarray[-1]
?).Tente adicionar essas instruções de depuração ao seu código. Você deve observar que, na iteração 10 ou 11,
array[i]
aponta parai
.fonte
array[10]
destruir o quadro da pilha, por exemplo. Como pode haver uma diferença entre o código com ou sem a saída de depuração? Se o endereço dei
nunca for necessário, o compilador pode otimizari
. em um registrador, alterando assim o layout de memória na pilha ...array[10]=0
. Se você compilou seu código com otimização, isso provavelmente não aconteceria. (Como C tem regras de aliasing que limitam que tipos de acesso à memória devem se sobrepor potencialmente a outra memória.Como uma variável local da qual você nunca usa o endereço, acho que um compilador deve ser capaz de assumir que nada o aliase. de uma matriz é um comportamento indefinido tente sempre difícil evitar dependendo isso..O erro está entre estes trechos de código:
Como
array
possui apenas 10 elementos, na última iteraçãoarray[10] = 0;
há um estouro de buffer. Os estouros de buffer são COMPORTAMENTO NÃO DEFINIDO , o que significa que eles podem formatar seu disco rígido ou causar demônios a sair do seu nariz.É bastante comum que todas as variáveis de pilha sejam dispostas adjacentes umas às outras. Se
i
estiver localizado ondearray[10]
grava, o UB será redefinidoi
para0
, levando ao loop não terminado.Para corrigir, altere a condição do loop para
i < 10
.fonte
rm -rf /
mesmo quando você não é root, não "formatar" toda a unidade, é claro, mas ainda assim destruindo todos os seus dados. Ai.Na qual deve ser a última execução do loop, você escreve
array[10]
, mas existem apenas 10 elementos na matriz, numerados de 0 a 9. A especificação da linguagem C diz que esse é um "comportamento indefinido". O que isso significa na prática é que seu programa tentará gravar naint
parte de tamanho de memória que fica imediatamente depoisarray
na memória. O que acontece, então, depende do que realmente existe, e isso depende não apenas do sistema operacional, mas também do compilador, das opções do compilador (como configurações de otimização), da arquitetura do processador, do código circundante , etc. Pode até variar de execução para execução, por exemplo, devido à aleatorização do espaço de endereço (provavelmente não neste exemplo de brinquedo, mas acontece na vida real). Algumas possibilidades incluem:i
. O loop nunca termina porquei
reinicia em 0.array
fica no final de uma página de memória virtual e a página seguinte não é mapeada.O que você observou no Windows foi que o compilador decidiu colocar a variável
i
imediatamente após a matriz na memória earray[10] = 0
acabou atribuindo-ai
. No Ubuntu e CentOS, o compilador não foi colocadoi
lá. Quase todas as implementações C agrupam variáveis locais na memória, em uma pilha de memória , com uma grande exceção: algumas variáveis locais podem ser colocadas inteiramente em registradores . Mesmo que a variável esteja na pilha, a ordem das variáveis é determinada pelo compilador e pode depender não apenas da ordem no arquivo de origem, mas também de seus tipos (para evitar desperdiçar memória com restrições de alinhamento que deixariam buracos) , em seus nomes, em algum valor de hash usado na estrutura de dados interna de um compilador, etc.Se você quiser descobrir o que o seu compilador decidiu fazer, você pode pedir para mostrar o código do assembler. Ah, e aprenda a decifrar o assembler (é mais fácil do que escrevê-lo). Com o GCC (e alguns outros compiladores, especialmente no mundo Unix), passe a opção
-S
de produzir código assembler em vez de binário. Por exemplo, aqui está o snippet do assembler para o loop compilar com o GCC no amd64 com a opção de otimização-O0
(sem otimização), com comentários adicionados manualmente:Aqui, a variável
i
está 52 bytes abaixo do topo da pilha, enquanto a matriz inicia 48 bytes abaixo do topo da pilha. Portanto, esse compilador foi colocadoi
logo antes da matriz; você substituiriai
se escrevessearray[-1]
. Se você mudararray[i]=0
paraarray[9-i]=0
, obterá um loop infinito nessa plataforma específica com essas opções específicas do compilador.Agora vamos compilar seu programa com
gcc -O1
.Isso é mais curto! O compilador não apenas se recusou a alocar um local de pilha
i
- apenas está armazenado no registroebx
- como também não se preocupou em alocar memória paraarray
ou gerar código para definir seus elementos, porque percebeu que nenhum dos elementos são sempre usados.Para tornar este exemplo mais revelador, vamos garantir que as atribuições da matriz sejam executadas, fornecendo ao compilador algo que não é possível otimizar. Uma maneira fácil de fazer isso é usar a matriz de outro arquivo - por causa da compilação separada, o compilador não sabe o que acontece em outro arquivo (a menos que seja otimizado no momento do link, o que ocorre
gcc -O0
ougcc -O1
não). Crie um arquivo de origemuse_array.c
contendoe mude seu código fonte para
Ajuntar com
Desta vez, o código do assembler fica assim:
Agora a matriz está na pilha, a 44 bytes da parte superior. Que tal
i
? Não aparece em lugar nenhum! Mas o contador de loop é mantido no registrorbx
. Não é exatamentei
, mas o endereço doarray[i]
. O compilador decidiu que, como o valor dei
nunca foi usado diretamente, não havia sentido em executar aritmética para calcular onde armazenar 0 durante cada execução do loop. Em vez disso, esse endereço é a variável do loop, e a aritmética para determinar os limites foi realizada em parte no tempo de compilação (multiplique 11 iterações por 4 bytes por elemento da matriz para obter 44) e parcialmente no tempo de execução, mas de uma vez por todas antes do início do loop ( faça uma subtração para obter o valor inicial).Mesmo neste exemplo muito simples, vimos como alterar as opções do compilador (ativar a otimização) ou alterar algo menor (
array[i]
paraarray[9-i]
) ou até alterar algo aparentemente não relacionado (adicionar a chamada parause_array
) pode fazer uma diferença significativa no que o programa executável gerou pelo compilador faz. As otimizações do compilador podem fazer muitas coisas que podem parecer não intuitivas em programas que invocam um comportamento indefinido . É por isso que o comportamento indefinido é deixado completamente indefinido. Quando você se desvia um pouco das trilhas, em programas do mundo real, pode ser muito difícil entender a relação entre o que o código faz e o que deveria ter feito, mesmo para programadores experientes.fonte
Ao contrário de Java, C não faz verificação de limite de matriz, ou seja, não há
ArrayIndexOutOfBoundsException
, o trabalho de garantir que o índice da matriz é válido é deixado para o programador. Fazer isso de propósito leva a um comportamento indefinido, qualquer coisa pode acontecer.Para uma matriz:
índices são válidos apenas no intervalo
0
de9
. No entanto, você está tentando:acesse
array[10]
aqui, altere a condição parai < 10
fonte
Você tem uma violação de limites e, nas plataformas sem terminação, acredito que você está inadvertidamente configurando
i
como zero no final do loop, para que ele reinicie novamente.array[10]
é inválido; contém 10 elementos,array[0]
atéarray[9]
earray[10]
é o 11º. Seu loop deve ser escrito para parar antes10
, da seguinte maneira:Onde o
array[10]
land é definido pela implementação e, de maneira divertida, em duas de suas plataformas, ele aterra noi
qual essas plataformas aparentemente colocam diretamente em seguidaarray
.i
é definido como zero e o loop continua para sempre. Para suas outras plataformas,i
pode estar localizado antesarray
ouarray
pode ter algum preenchimento depois dele.fonte
Você declara que o
int array[10]
meioarray
possui índice0
para9
(total de10
elementos inteiros que ele pode conter). Mas o seguinte loop,fará um loop
0
para10
significa11
tempo. Portanto, quandoi = 10
ele excederá o buffer e causará um comportamento indefinido .Então tente o seguinte:
ou,
fonte
Ele é indefinido em
array[10]
e fornece um comportamento indefinido como descrito anteriormente. Pense assim:Tenho 10 itens no meu carrinho de compras. Eles são:
0: Uma caixa de cereal
1: Pão
2: Leite
3: Torta
4: Ovos
5: Bolo
6: A 2 litro de refrigerante
7: Salada
8: Hambúrgueres
9: Sorvete
cart[10]
é indefinido e pode dar uma exceção fora dos limites em alguns compiladores. Mas, aparentemente, muitas não. O décimo primeiro item aparente é um item que não está realmente no carrinho. O décimo primeiro item está apontando para o que chamarei de "item poltergeist". Isso nunca existiu, mas estava lá.Por que alguns compiladores fornecem
i
um índice dearray[10]
ouarray[11]
ou mesmoarray[-1]
é por causa da sua declaração de inicialização / declaração. Alguns compiladores interpretam isso como:int
s paraarray[10]
e outroint
bloco. Para facilitar, coloque-os um ao lado do outro."array[10]
isso não apontei
.i
emarray[-1]
(porque um índice de uma matriz não pode ou não deveria ser negativo) ou aloque-o em um local completamente diferente, porque o sistema operacional pode lidar com isso e é mais seguro.Alguns compiladores querem que as coisas aconteçam mais rapidamente e outros preferem segurança. É tudo sobre o contexto. Se eu estivesse desenvolvendo um aplicativo para o antigo BREW OS (o sistema operacional de um telefone básico), por exemplo, ele não se importaria com a segurança. Se eu estivesse desenvolvendo para um iPhone 6, ele poderia correr rápido, não importando o que fosse, então eu precisaria enfatizar a segurança. (Sério, você leu as Diretrizes da App Store da Apple ou leu o desenvolvimento do Swift e Swift 2.0?)
fonte
Como você criou uma matriz de tamanho 10, a condição de loop deve ser a seguinte:
Atualmente, você está tentando acessar o local não atribuído a partir da memória
array[10]
e está causando o comportamento indefinido . Comportamento indefinido significa que seu programa se comportará de maneira indeterminada, para que ele possa fornecer resultados diferentes em cada execução.fonte
Bem, o compilador C tradicionalmente não verifica limites. Você pode receber uma falha de segmentação caso se refira a um local que não "pertence" ao seu processo. No entanto, as variáveis locais são alocadas na pilha e, dependendo da maneira como a memória é alocada, a área logo após a matriz (
array[10]
) pode pertencer ao segmento de memória do processo. Portanto, nenhuma armadilha de falha de segmentação é lançada e é isso que você parece experimentar. Como outros já apontaram, esse é um comportamento indefinido em C e seu código pode ser considerado irregular. Como você está aprendendo C, é melhor você adquirir o hábito de verificar se há limites no seu código.fonte
Além da possibilidade de que a memória possa ser disposta para que uma tentativa de gravação seja
a[10]
realmente substituídai
, também seria possível que um compilador de otimização determine que o teste de loop não pode ser alcançado com um valori
maior que dez sem que o código tenha acessado primeiro o elemento de matriz inexistentea[10]
.Como uma tentativa de acessar esse elemento seria um comportamento indefinido, o compilador não teria obrigações com relação ao que o programa poderia fazer após esse ponto. Mais especificamente, como o compilador não teria obrigação de gerar código para verificar o índice do loop em nenhum caso em que pudesse ser maior que dez, não teria obrigação de gerar código para verificá-lo; em vez disso, poderia assumir que o
<=10
teste sempre produzirá verdade. Observe que isso seria verdade mesmo se o código fosse lido ema[10]
vez de gravado.fonte
Quando você repete,
i==9
você atribui zero aos 'itens da matriz' que estão realmente localizados após a matriz , para substituir outros dados. Provavelmente você sobrescreve ai
variável, que está localizada depoisa[]
. Dessa forma, você simplesmente redefine ai
variável para zero e, assim, reinicia o loop.Você pode descobrir isso sozinho se imprimisse
i
no loop:em vez de apenas
É claro que esse resultado depende fortemente da alocação de memória para suas variáveis, que por sua vez depende de um compilador e de suas configurações, por isso geralmente é um comportamento indefinido - é por isso que os resultados em diferentes máquinas ou sistemas operacionais ou em diferentes compiladores podem ser diferentes.
fonte
o erro está no array de porções [10] w / c também é o endereço de i (int array [10], i;). quando o array [10] é definido como 0, o i seria 0 w / c redefine o loop inteiro e causa o loop infinito. haverá loop infinito se o array [10] estiver entre 0-10. o loop correto deve ser para (i = 0; i <10; i ++) {...} int array [10], i; para (i = 0; i <= 10; i ++) matriz [i] = 0;
fonte
Vou sugerir algo que não encontro acima:
Tente atribuir matriz [i] = 20;
Eu acho que isso deve terminar o código em todos os lugares .. (desde que você mantenha i <= 10 ou ll)
Se isso funcionar, você pode decidir com firmeza que as respostas especificadas aqui já estão corretas [a resposta relacionada à memória que apaga uma por ex.]
fonte
Há duas coisas erradas aqui. O int i é na verdade um elemento do array, array [10], como visto na pilha. Como você permitiu que a indexação realmente fizesse a matriz [10] = 0, o índice do loop, i, nunca excederá 10. Faça isso
for(i=0; i<10; i+=1)
.O i ++ é, como K&R o chamaria, 'estilo ruim'. Ele está incrementando i pelo tamanho de i, e não 1. i ++ é para matemática de ponteiros e i + = 1 é para álgebra. Embora isso dependa do compilador, não é uma boa convenção para portabilidade.
fonte
i
NÃO é um elemento da matriza[10]
, não há obrigação ou mesmo sugestão para um compilador colocá-lo na pilha imediatamente apósa[]
- ele também pode estar localizado antes da matriz ou separado com algum espaço adicional. Pode até ser alocado fora da memória principal, por exemplo, em um registro da CPU. Também é falso que++
seja para ponteiros e não para números inteiros. Completamente errado é 'i ++ está aumentando i pelo tamanho de i' - leia a descrição do operador na definição da linguagem!i
- é doint
tipo. É um número inteiro , não um ponteiro; um inteiro, usado como um índice paraarray
,.