Sua pergunta provavelmente não foi "Por que essas construções são um comportamento indefinido em C?". Sua pergunta foi provavelmente: "Por que esse código (usando ++
) não me deu o valor que eu esperava?", E alguém marcou sua pergunta como duplicada e o enviou aqui.
Esta resposta tenta responder a essa pergunta: por que seu código não deu a resposta que você esperava e como você pode aprender a reconhecer (e evitar) expressões que não funcionarão conforme o esperado.
Suponho que você tenha ouvido a definição básica de C ++
e--
operadores e como o formulário de prefixo ++x
difere do formulário de postfix x++
. Mas é difícil pensar nesses operadores; portanto, para ter certeza de que você entendeu, talvez você tenha escrito um pequeno programa de teste envolvendo algo como
int x = 5;
printf("%d %d %d\n", x, ++x, x++);
Mas, para sua surpresa, este programa não o ajudou a entender - ele imprimiu uma saída estranha, inesperada e inexplicável, sugerindo que talvez ++
faça algo completamente diferente, nem um pouco o que você pensou que ele fez.
Ou talvez você esteja vendo uma expressão difícil de entender como
int x = 5;
x = x++ + ++x;
printf("%d\n", x);
Talvez alguém tenha lhe dado esse código como um quebra-cabeça. Esse código também não faz sentido, especialmente se você o executa - e se você o compilar e executar em dois compiladores diferentes, é provável que você obtenha duas respostas diferentes! O que há com isso? Qual resposta está correta? (E a resposta é que ambos são, ou nenhum deles é.)
Como você já ouviu falar agora, todas essas expressões são indefinidas , o que significa que a linguagem C não garante o que fará. Esse é um resultado estranho e surpreendente, porque você provavelmente pensou que qualquer programa que pudesse escrever, contanto que fosse compilado e executado, geraria uma saída única e bem definida. Mas no caso de comportamento indefinido, não é assim.
O que torna uma expressão indefinida? As expressões que envolvem ++
e--
sempre indefinidas? Claro que não: esses são operadores úteis e, se você os usar corretamente, serão perfeitamente bem definidos.
Para as expressões em que estamos falando, o que as torna indefinidas é quando há muita coisa acontecendo ao mesmo tempo, quando não sabemos ao certo em que ordem as coisas acontecerão, mas quando a ordem importa para o resultado que obtemos.
Vamos voltar aos dois exemplos que usei nesta resposta. Quando eu escrevi
printf("%d %d %d\n", x, ++x, x++);
a questão é, antes de chamar printf
, o compilador calcula o valor de x
first, ou x++
, ou talvez ++x
? Mas acontece que não sabemos . Não existe uma regra em C que diga que os argumentos para uma função sejam avaliados da esquerda para a direita ou da direita para a esquerda ou em alguma outra ordem. Portanto, não podemos dizer se o compilador vai fazer x
em primeiro lugar, então ++x
, em seguida x++
, ou x++
então ++x
, em seguida x
, ou algum outro fim. Mas a ordem é claramente importante, porque, dependendo de qual ordem o compilador usa, obteremos claramente diferentes resultados impressos printf
.
E essa expressão maluca?
x = x++ + ++x;
O problema com esta expressão é que ela contém três tentativas diferentes para modificar o valor de x: (1) a x++
parte tenta adicionar 1 a x, armazenar o novo valor em x
e retornar o valor antigo de x
; (2) a ++x
peça tenta adicionar 1 a x, armazenar o novo valor em x
e retornar o novo valor de x
; e (3) a x =
parte tenta atribuir a soma dos outros dois de volta a x. Qual dessas três tentativas de atribuição "vencerá"? A qual dos três valores será atribuído x
? Novamente, e talvez surpreendentemente, não há regra em C para nos dizer.
Você pode imaginar que a precedência ou associatividade ou a avaliação da esquerda para a direita informa em que ordem as coisas acontecem, mas elas não acontecem. Você pode não acreditar em mim, mas aceite minha palavra e digo novamente: precedência e associatividade não determinam todos os aspectos da ordem de avaliação de uma expressão em C. Em particular, se em uma expressão houver várias pontos diferentes em que tentamos atribuir um novo valor a algo como x
precedência e associatividade não nos dizem qual dessas tentativas acontece primeiro, ou último, ou algo assim.
Portanto, com todo esse histórico e introdução fora do caminho, se você quiser garantir que todos os seus programas estejam bem definidos, quais expressões você pode escrever e quais você não pode escrever?
Essas expressões são boas:
y = x++;
z = x++ + y++;
x = x + 1;
x = a[i++];
x = a[i++] + b[j++];
x[i++] = a[j++] + b[k++];
x = *p++;
x = *p++ + *q++;
Essas expressões são todas indefinidas:
x = x++;
x = x++ + ++x;
y = x + x++;
a[i] = i++;
a[i++] = i;
printf("%d %d %d\n", x, ++x, x++);
E a última pergunta é: como você pode dizer quais expressões estão bem definidas e quais são indefinidas?
Como eu disse anteriormente, as expressões indefinidas são aquelas em que há muita coisa ao mesmo tempo, onde você não pode ter certeza de qual ordem as coisas acontecem e onde a ordem importa:
- Se houver uma variável que está sendo modificada (atribuída a) em dois ou mais locais diferentes, como você sabe qual modificação acontece primeiro?
- Se houver uma variável que está sendo modificada em um local e tendo seu valor usado em outro local, como você sabe se ela usa o valor antigo ou o novo valor?
Como um exemplo de # 1, na expressão
x = x++ + ++x;
existem três tentativas para modificar o `x.
Como um exemplo de # 2, na expressão
y = x + x++;
nós dois usamos o valor de x
e o modificamos.
Portanto, essa é a resposta: verifique se, em qualquer expressão que você escreve, cada variável é modificada no máximo uma vez e, se uma variável é modificada, você também não tenta usar o valor dessa variável em outro lugar.