C ++ 98 e C ++ 03
Esta resposta é para as versões mais antigas do padrão C ++. As versões C ++ 11 e C ++ 14 do padrão não contêm formalmente 'pontos de sequência'; operações são 'sequenciadas antes' ou 'não sequenciadas' ou 'indeterminadamente sequenciadas'. O efeito líquido é essencialmente o mesmo, mas a terminologia é diferente.
Disclaimer : Ok. Essa resposta é um pouco longa. Portanto, tenha paciência ao lê-lo. Se você já sabe essas coisas, lê-las novamente não o deixará louco.
Pré-requisitos : Um conhecimento elementar do Padrão C ++
O que são pontos de sequência?
O Padrão diz
Em certos pontos especificados na sequência de execução chamados pontos de sequência , todos os efeitos colaterais das avaliações anteriores devem estar completos e nenhum efeito colateral das avaliações subsequentes deve ter ocorrido. (§1.9 / 7)
Efeitos colaterais? Quais são os efeitos colaterais?
A avaliação de uma expressão produz algo e se, além disso, há uma alteração no estado do ambiente de execução, diz-se que a expressão (sua avaliação) tem alguns efeitos colaterais.
Por exemplo:
int x = y++; //where y is also an int
Além da operação de inicialização, o valor de y
é alterado devido ao efeito colateral do ++
operador.
Por enquanto, tudo bem. Passando para os pontos de sequência. Uma definição de alternância de pontos seq fornecida pelo autor comp.lang.c Steve Summit
:
O ponto de sequência é um momento no qual a poeira se depositou e todos os efeitos colaterais vistos até agora são garantidos.
Quais são os pontos de sequência comuns listados no padrão C ++?
Esses são:
no final da avaliação da expressão completa ( §1.9/16
) (Uma expressão completa é uma expressão que não é uma subexpressão de outra expressão.) 1
Exemplo:
int a = 5; // ; is a sequence point here
na avaliação de cada uma das seguintes expressões após a avaliação da primeira expressão ( §1.9/18
) 2
a && b (§5.14)
a || b (§5.15)
a ? b : c (§5.16)
a , b (§5.18)
(aqui a, b é um operador de vírgula; in func(a,a++)
,
não é um operador de vírgula, é apenas um separador entre os argumentos a
e a++
. Portanto, o comportamento é indefinido nesse caso (se a
for considerado um tipo primitivo))
em uma chamada de função (esteja a função em linha ou não), após a avaliação de todos os argumentos da função (se houver) que ocorrem antes da execução de quaisquer expressões ou instruções no corpo da função ( §1.9/17
).
1: Nota: a avaliação de uma expressão completa pode incluir a avaliação de subexpressões que não fazem parte lexicamente da expressão completa. Por exemplo, subexpressões envolvidas na avaliação de expressões de argumento padrão (8.3.6) são consideradas criadas na expressão que chama a função, não na expressão que define o argumento padrão
2: Os operadores indicados são os operadores internos, conforme descrito na seção 5. Quando um desses operadores é sobrecarregado (seção 13) em um contexto válido, designando assim uma função de operador definida pelo usuário, a expressão designa uma chamada de função e os operandos formam uma lista de argumentos, sem um ponto de sequência implícito entre eles.
O que é comportamento indefinido?
O Padrão define Comportamento Indefinido na Seção §1.3.12
como
comportamento, como pode surgir com o uso de uma construção de programa ou dados errados, para os quais esta Norma Internacional não impõe requisitos 3 .
Um comportamento indefinido também pode ser esperado quando esta Norma Internacional omite a descrição de qualquer definição explícita de comportamento.
3: o comportamento indefinido permitido varia de ignorar completamente a situação com resultados imprevisíveis, comportar-se durante a tradução ou a execução do programa de maneira documentada característica do ambiente (com ou sem a emissão de uma mensagem de diagnóstico), até o término de uma tradução ou execução (com a emissão de uma mensagem de diagnóstico).
Em resumo, um comportamento indefinido significa que qualquer coisa pode acontecer, desde daemons voando pelo nariz até a namorada engravidar.
Qual é a relação entre comportamento indefinido e pontos de sequência?
Antes de entrar nesse assunto, você deve conhecer as diferenças entre comportamento indefinido, comportamento não especificado e comportamento definido para implementação .
Você também deve saber disso the order of evaluation of operands of individual operators and subexpressions of individual expressions, and the order in which side effects take place, is unspecified
.
Por exemplo:
int x = 5, y = 6;
int z = x++ + y++; //it is unspecified whether x++ or y++ will be evaluated first.
Outro exemplo aqui .
Agora, o padrão §5/4
diz
- 1) Entre o ponto de sequência anterior e o próximo, um objeto escalar deve ter seu valor armazenado modificado no máximo uma vez pela avaliação de uma expressão.
O que isso significa?
Informalmente, significa que entre dois pontos de sequência uma variável não deve ser modificada mais de uma vez. Em uma declaração de expressão, next sequence point
geralmente está no ponto-e-vírgula final e previous sequence point
no final da declaração anterior. Uma expressão também pode conter intermediários sequence points
.
Na sentença acima, as seguintes expressões invocam o comportamento indefinido:
i++ * ++i; // UB, i is modified more than once btw two SPs
i = ++i; // UB, same as above
++i = 2; // UB, same as above
i = ++i + 1; // UB, same as above
++++++i; // UB, parsed as (++(++(++i)))
i = (i, ++i, ++i); // UB, there's no SP between `++i` (right most) and assignment to `i` (`i` is modified more than once btw two SPs)
Mas as seguintes expressões são boas:
i = (i, ++i, 1) + 1; // well defined (AFAIK)
i = (++i, i++, i); // well defined
int j = i;
j = (++i, i++, j*i); // well defined
- 2) Além disso, o valor anterior deve ser acessado apenas para determinar o valor a ser armazenado.
O que isso significa? Isso significa que, se um objeto é gravado em uma expressão completa, todo e qualquer acesso a ele dentro da mesma expressão deve estar diretamente envolvido no cálculo do valor a ser gravado .
Por exemplo, em i = i + 1
todo o acesso de i
(no LHS e no RHS) estão diretamente envolvidos no cálculo do valor a ser gravado. Então está tudo bem.
Essa regra restringe efetivamente expressões legais àquelas em que os acessos precedem comprovadamente a modificação.
Exemplo 1:
std::printf("%d %d", i,++i); // invokes Undefined Behaviour because of Rule no 2
Exemplo 2:
a[i] = i++ // or a[++i] = i or a[i++] = ++i etc
é proibido porque um dos acessos de i
(aquele em a[i]
) não tem nada a ver com o valor que acaba sendo armazenado em i (o que acontece em i++
), e, portanto, não há uma boa maneira de definir - tanto para o nosso entendimento quanto para o compilador - se o acesso deve ocorrer antes ou depois do valor incrementado ser armazenado. Portanto, o comportamento é indefinido.
Exemplo 3:
int x = i + i++ ;// Similar to above
Resposta de acompanhamento para C ++ 11 aqui .
*p++ = 4
não é um comportamento indefinido.*p++
é interpretado como*(p++)
.p++
retornap
(uma cópia) e o valor armazenado no endereço anterior. Por que isso invocaria o UB? Está perfeitamente bem.