c++ - portugues - resolva os problemas




Comportamento indefinido e pontos de sequência (4)

O que são "pontos de sequência"?

Qual é a relação entre comportamento indefinido e pontos de seqüência?

Costumo usar expressões engraçadas e complicadas como a[++i] = i; , para me fazer sentir melhor. Por que eu deveria parar de usá-los?

Se você leu isso, não deixe de visitar a pergunta de acompanhamento Comportamento indefinido e pontos de sequência recarregados .

(Nota: Esta é uma entrada para o C ++ FAQ do Stack Overflow . Se você quiser criticar a idéia de fornecer um FAQ neste formulário, então o post no meta que iniciou tudo isso seria o lugar para fazer isso. essa questão é monitorada na sala de chat do C ++ , onde a ideia do FAQ começou em primeiro lugar, então é muito provável que sua resposta seja lida por aqueles que surgiram com a ideia.)


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 seqüência'; operações são 'seqüenciadas antes' ou 'não seqüenciadas' ou 'indeterminadamente seqüenciadas'. O efeito líquido é essencialmente o mesmo, mas a terminologia é diferente.

Disclaimer : Ok. Essa resposta é um pouco longa. Então tenha paciência enquanto lê-lo. Se você já conhece essas coisas, lê-las novamente não vai deixar você louco.

Pré-requisitos : um conhecimento elementar do padrão C ++

O que são pontos de seqüência?

O Standard diz

Em certos pontos específicos da seqüência de execução, denominados pontos de seqüência , todos os efeitos colaterais de avaliações anteriores devem estar completos e não devem ter ocorrido efeitos colaterais de avaliações subseqüentes. (§ 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 houver uma mudança no estado do ambiente de execução, é dito que a expressão (sua avaliação) tem algum efeito colateral (s).

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 da sequência. Uma definição de alternância de seq-points dada pelo autor de comp.lang.c Steve Summit :

O ponto de seqüência é um ponto no tempo em que a poeira se instalou e todos os efeitos colaterais vistos até agora estão garantidos como completos.

Quais são os pontos de seqüência comuns listados no C ++ Standard?

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 vírgula; em func(a,a++) , não é um operador vírgula, é meramente um separador entre os argumentos a e a++ . Assim, o comportamento é indefinido nesse caso (se a é considerado um tipo primitivo))
  • em uma chamada de função (quer a função esteja ou não em linha), após a avaliação de todos os argumentos de 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 são lexicalmente parte da expressão completa. Por exemplo, subexpressões envolvidas na avaliação de expressões de argumentos padrão (8.3.6) são consideradas criadas na expressão que chama a função, não a expressão que define o argumento padrão

2: Os operadores indicados são os operadores integrados, conforme descrito na cláusula 5. Quando um desses operadores é sobrecarregado (cláusula 13) em um contexto válido, designando 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 seqüência implícito entre eles.

O que é um comportamento indefinido?

O Padrão define Comportamento Indefinido na Seção §1.3.12 como

comportamento, tal como pode surgir com o uso de uma construção de programa errônea ou dados errados, para os quais esta Norma não impõe requisitos 3 .

Comportamento indefinido também pode ser esperado quando esta Norma omite a descrição de qualquer definição explícita de comportamento.

3: o comportamento indefinido permissível varia de ignorar completamente a situação com resultados imprevisíveis, comportar-se durante a tradução ou execução do programa de uma maneira documentada característica do ambiente (com ou sem a emissão de uma mensagem de diagnóstico), encerrar uma tradução ou execução (com a emissão de uma mensagem de diagnóstico).

Em suma, o comportamento indefinido significa que tudo pode acontecer de daemons voando para fora do nariz até a namorada ficar grávida.

Qual é a relação entre o comportamento indefinido e os pontos de seqüência?

Antes de entrar nisso, você deve saber a (s) diferença (s) entre Comportamento Indefinido, Comportamento Não Especificado e Comportamento Definido por Implementação .

Você também deve saber que 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 here .

Agora o padrão em §5/4 diz

  • 1) Entre o ponto de seqüência anterior e o seguinte, 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 instrução de expressão, o next sequence point geralmente está no ponto-e-vírgula de terminação e o previous sequence point está no final da instrução anterior. Uma expressão também pode conter sequence points intermediários.

A partir da frase 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 é escrito dentro de 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 (em LHS e em RHS) está diretamente envolvido no cálculo do valor a ser escrito. Então está bem.

Essa regra efetivamente restringe expressões legais àquelas em que os acessos demonstram preceder 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

não é permitido 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 então não há uma boa maneira de definir - seja para nosso entendimento ou para o compilador - se o acesso deve ocorrer antes ou após o valor incrementado ser armazenado. Então o comportamento é indefinido.

Exemplo 3:

int x = i + i++ ;// Similar to above

Resposta de acompanhamento para o C ++ 11 here .


Em C99(ISO/IEC 9899:TC3) que parece ausente desta discussão, até o momento, os steteents seguintes são feitos em relação à ordem de avaliação.

[...] a ordem de avaliação das subexpressões e a ordem em que os efeitos colaterais ocorrem não são especificadas. (Seção 6.5 pp 67)

A ordem de avaliação dos operandos não é especificada. Se for feita uma tentativa de modificar o resultado de um operador de atribuição ou de acessá-lo após o próximo ponto de seqüência, o comportamento [sic] é indefinido (Seção 6.5.16, p. 91).


Este é um seguimento da minha resposta anterior e contém material relacionado ao C ++ 11. .

Pré-requisitos : Um conhecimento elementar de Relações (Matemática).

É verdade que não existem pontos de seqüência no C ++ 11?

Sim! Isso é verdade.

Os pontos de seqüência foram substituídos pelas relations Sequenced Before e Sequenced After (e Unsequenced and Indeterminately Sequenced ) em C ++ 11.

O que exatamente é essa coisa "Sequenciada antes"?

Sequenciado Antes (§1.9 / 13) é uma relação que é:

entre avaliações executadas por um único thread e induz uma ordem parcial estrita 1

Formalmente significa dar duas avaliações (veja abaixo) A e B , se A for seqüenciado antes de B , então a execução de A precederá a execução de B Se A não for sequenciado antes de B e B não serem sequenciados antes de A , então A e B serão desassociados 2 .

As avaliações A e B são sequenciadas indeterminadamente quando A é sequenciado antes de B ou B ser sequenciado antes de A , mas não é especificado que 3 .

[NOTAS]
1: Uma ordem parcial estrita é uma relação binária "<" sobre um conjunto P que é Asymmetric e Transitive , isto é, para todos os a , b e c em P , temos que:
........(Eu). se a <b então ¬ (b <a) ( asymmetry );
........ (ii) se a <b e b <c então a <c ( transitivity ).
2: A execução de avaliações não sequenciadas pode se sobrepor .
3: Avaliações sequenciadas indeterminadas não podem se sobrepor , mas podem ser executadas primeiro.

Qual é o significado da palavra 'avaliação' no contexto do C ++ 11?

Em C ++ 11, a avaliação de uma expressão (ou uma sub-expressão) em geral inclui:

  • cálculos de valor (incluindo a determinação da identidade de um objeto para avaliação glvalue e a busca de um valor previamente atribuído a um objeto para avaliação de valor ) e

  • iniciação de efeitos colaterais .

Agora (§ 1.9 / 14) diz:

Cada cálculo de valor e efeito colateral associado a uma expressão completa é sequenciado antes de cada cálculo de valor e efeito colateral associado à próxima expressão completa a ser avaliada .

  • Exemplo trivial:

    int x; x = 10; ++x;

    A computação de valor e o efeito colateral associados a ++x são sequenciados após o cálculo do valor e o efeito colateral de x = 10;

Portanto, deve haver alguma relação entre o comportamento indefinido e as coisas acima mencionadas, certo?

Sim! Certo.

Em (§1.9 / 15) foi mencionado que

Exceto onde indicado, avaliações de operandos de operadores individuais e de subexpressões de expressões individuais são desassociadas 4 .

Por exemplo :

int main()
{
     int num = 19 ;
     num = (num << 3) + (num >> 3);
} 
  1. Avaliação de operandos do operador + são sequenciados um em relação ao outro.
  2. Avaliação de operandos de << e >> operadores não são sequenciados em relação um ao outro.

4: Em uma expressão que é avaliada mais de uma vez durante a execução de um programa, as avaliações não seqüenciadas e indeterminadas de suas subexpressões não precisam ser realizadas consistentemente em diferentes avaliações.

(§1.9 / 15) Os cálculos de valor dos operandos de um operador são sequenciados antes do cálculo do valor do resultado do operador.

Isso significa que em x + y o cálculo de valor de y é sequenciado antes do cálculo do valor de (x + y) .

Mais importante

(§1.9 / 15) Se um efeito colateral em um objeto escalar não é sequenciado em relação

(a) outro efeito colateral no mesmo objeto escalar

ou

(b) um cálculo de valor usando o valor do mesmo objeto escalar.

o comportamento é indefinido .

Exemplos:

int i = 5, v[10] = { };
void  f(int,  int);
  1. i = i++ * ++i; // Undefined Behaviour
  2. i = ++i + i++; // Undefined Behaviour
  3. i = ++i + ++i; // Undefined Behaviour
  4. i = v[i++]; // Undefined Behaviour
  5. i = v[++i]: // Well-defined Behavior
  6. i = i++ + 1; // Undefined Behaviour
  7. i = ++i + 1; // Well-defined Behaviour
  8. ++++i; // Well-defined Behaviour
  9. f(i = -1, i = -1); // Undefined Behaviour (see below)

Ao chamar uma função (independentemente de a função estar ou não inline), todo cálculo de valor e efeito colateral associado a qualquer expressão de argumento, ou com a expressão postfix designando a função chamada, é sequenciado antes da execução de cada expressão ou instrução no corpo do chamada função. [ Nota: Cálculos de valor e efeitos colaterais associados a expressões de argumentos diferentes não são sequenciados . - nota final

Expressões (5) , (7) e (8) não invocam comportamento indefinido. Confira as seguintes respostas para uma explicação mais detalhada.

Nota final :

Se você encontrar alguma falha no post, por favor deixe um comentário. Usuários avançados (com rep> 20000), por favor, não hesite em editar o post para corrigir erros de digitação e outros erros.


O C ++ 17 ( N4659 ) inclui uma proposta, a Ordem de Avaliação de Expressão de Refinamento para o C ++ idiomático, que define uma ordem mais estrita de avaliação de expressão.

Em particular, foi acrescentada a seguinte frase :

8.18 Operadores de atribuição e atribuição composta :
....

Em todos os casos, a atribuição é sequenciada após o cálculo do valor dos operandos direito e esquerdo, e antes do cálculo do valor da expressão de atribuição. O operando direito é sequenciado antes do operando esquerdo.

Isso torna vários casos de comportamento previamente indefinido válidos, incluindo o que está em questão:

a[++i] = i;

No entanto, vários outros casos semelhantes ainda levam a um comportamento indefinido.

Em N4140 :

i = i++ + 1; // the behavior is undefined

Mas em N4659

i = i++ + 1; // the value of i is incremented
i = i++ + i; // the behavior is undefined

Obviamente, usar um compilador compatível com C ++ 17 não significa necessariamente que alguém deva começar a escrever tais expressões.





sequence-points