O que são pontos de sequência em C++ e qual a relação com comportamento indefinido?

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

A arma secreta do depurador: o que programadores C++ ignoram

\n\n

Você já escreveu algo como i = i++ + ++i; e obteve resultados diferentes em compiladores diferentes? Ou já pegou um bug estranho que só aparecia no servidor, mas não no seu notebook? Conheça: undefined behavior (comportamento indefinido) — um dos inimigos mais traiçoeiros do desenvolvedor C++. E no centro desse caos estão os pontos de sequência (sequence points).

\n\n

Neste artigo, vamos entender o que são sequence points, como eles se relacionam com a ordem de avaliação em C++ e — o mais importante — como não pisar nessas armadilhas. O material é baseado em uma pergunta clássica do Stack Overflow, que recebeu milhares de visualizações. Vamos nessa!

\n\n

O que são pontos de sequência (sequence points)?

\n\n

Imagine que você é o maestro de uma orquestra. Cada músico (instrução do processador) deve entrar em um momento estritamente definido. Um ponto de sequência é um momento em que todos os "efeitos colaterais" anteriores (alterações em variáveis, chamadas de função) já foram garantidamente concluídos, e os próximos ainda não começaram.

\n\n

No padrão C++ 98/03, usava-se o termo sequence point. No C++11, ele foi substituído pelas relações mais rigorosas "sequenciado antes / sequenciado depois", mas a essência permaneceu: entre dois pontos de sequência, o compilador tem total liberdade para reordenar as operações, desde que isso não altere o resultado (de acordo com a regra "as-if").

\n\n

Simplificando: se você tenta modificar a mesma variável duas vezes entre dois pontos de sequência — você obtém undefined behavior. O compilador pode fazer qualquer coisa: desde um resultado correto até formatar seu disco rígido (brincadeira, mas quase verdade).

\n\n

Exemplos clássicos de pontos de sequência

\n
    \n
  • Ponto e vírgula ; — fim de uma expressão
  • \n
  • Vírgula , como operador (mas não em argumentos de função!)
  • \n
  • Fim de cada expressão em for (após inicialização, teste e passo)
  • \n
  • Operadores lógicos && e ||
  • \n
  • Operador ternário ?:
  • \n
  • Chamada de função (antes do corpo da função começar a executar)
  • \n
\n\n

Como os pontos de sequência geram comportamento indefinido?

\n\n

Aqui está um exemplo clássico que todo desenvolvedor C++ deveria conhecer:

\n\n
int i = 0;\ni = i++ + ++i;  // UB! Comportamento indefinido
\n\n

Por que isso é UB? Porque entre dois pontos de sequência (neste caso, entre o início e o fim da expressão) a variável i é modificada duas vezes:

\n
    \n
  1. Incremento pós-fixado i++
  2. \n
  3. Incremento pré-fixado ++i
  4. \n
  5. Atribuição i = ...
  6. \n
\n\n

O compilador não sabe em qual ordem executar essas operações. Resultado? Um compilador retorna 2, outro retorna 3, um terceiro retorna 0. E no modo de otimização, pode simplesmente descartar esse código.

\n\n

Outro caso complicado: argumentos de função

\n\n
int foo(int a, int b) { return a + b; }\n\nint x = 5;\nint result = foo(++x, ++x);  // UB!
\n\n

Aqui, a vírgula entre os argumentos não é um operador vírgula, mas um separador. Não há ponto de sequência entre a avaliação dos argumentos ++x! Ambos os incrementos são executados "em paralelo" (do ponto de vista do padrão), e novamente temos undefined behavior.

\n\n

Exemplos práticos: como o UB se parece em código real

\n\n

Exemplo 1: Bug não óbvio com indexação

\n\n
int arr[5] = {0, 10, 20, 30, 40};\nint idx = 1;\narr[idx] = idx++;  // UB!
\n\n

À primeira vista, parece simples: queremos escrever 1 em arr[1] e depois incrementar idx. Mas o padrão não garante a ordem de avaliação do índice idx e do lado direito idx++. Resultado? Pode escrever em arr[1] ou em arr[2].

\n\n

Exemplo 2: Compl

Blogs

Book Recommendations