Saltar para o conteúdo
Topos Uranos

Atribuição, efeitos colaterais e comportamento indefinido

Resumo

Uma expressão de C++ pode fazer duas coisas distintas: dar um valor e mudar o estado do programa. x + 1 só dá um valor; x = 7 dá um valor e, além disso, muda x; x++ dá o valor que x tinha e deixa outro em x. Esta aula estuda essa diferença entre o valor e o efeito de uma expressão: a atribuição como expressão, a atribuição composta, o incremento e o decremento nas suas formas prefixa e pós-fixa, e a ordem em que se avaliam as partes de uma expressão, que o padrão fixa só em parte. Daí se segue a pergunta que ordena o resto da aula: o que o padrão promete acerca de um programa. A resposta é um catálogo de quatro graus, do comportamento definido ao indefinido, que reúne todos os casos vistos até aqui e diz, para cada um, o que o detecta (o compilador, o detector, a biblioteca) e o que ninguém detecta.

Objetivos de aprendizagem

  1. Distinguir o valor de uma expressão do seu efeito colateral em atribuições, incrementos e chamadas.
  2. Prever o resultado de x++ e ++x usados como valor, e rastrear à mão um programa com atribuições compostas.
  3. Reconhecer expressões que modificam duas vezes uma variável, ou que a leem e a modificam, sem uma ordem fixada pelo padrão, e reescrevê-las em instruções separadas.
  4. Classificar uma construção como definida, definida pela implementação, não especificada ou indefinida, com um exemplo de cada categoria.
  5. Dizer, para cada comportamento indefinido do catálogo, se o compilador avisa, se o detector ou a biblioteca o detêm, ou se nenhum deles o detecta.

O valor e o efeito

A aula Aritmética estudou a expressão como uma árvore que se reduz a um valor: 2 + 3 * 4 vale 14, e calculá-la não muda nada fora da própria expressão. A aula Relações e lógica fez o mesmo com as condições, cujo valor é true ou false. Ora, há expressões que, além de dar um valor, deixam uma marca no estado do programa: mudam o valor de uma variável, escrevem na tela ou leem da entrada. Essa marca chama-se efeito colateral, ou simplesmente efeito, e a palavra “colateral” não quer dizer que importe menos: em x = 7 o efeito, que x passe a valer 7, é justamente o que se procura.

Se isto é assim, toda expressão deve ser lida com duas perguntas: o que ela dá e o que ela faz. Três exemplos fixam a distinção. count + 1 dá o sucessor de count e não faz nada: count conserva o seu valor. count = 9 dá 9 e faz com que count valha 9. std::println("olá") também é uma expressão, uma chamada a uma função, e o que importa nela é o seu efeito: escrever uma linha. As funções próprias serão estudadas na aula Funções, e ali a distinção reaparecerá: uma função que só calcula o seu resultado é mais fácil de entender e de testar do que uma que, além disso, muda algo fora dela.

Uma instrução como count + 1; é C++ válido: calcula-se o valor e descarta-se, de modo que não serve para nada, e o GCC avisa com -Wunused-value. Uma instrução de expressão tem sentido pelo seu efeito. Daí se segue que as expressões com efeito são as que movem o programa, e também as que exigem cuidado, porque o efeito ocorre em algum momento, e esse momento nem sempre é o que o leitor supõe.