Recentemente, em uma entrevista, houve o seguinte tipo de questão objetiva.
int a = 0;
cout << a++ << a;
Respostas:
uma. 10
b. 01
c. comportamento indefinido
Respondi a opção b, ou seja, a saída seria "01".
Mas, para minha surpresa, mais tarde, um entrevistador me disse que a resposta correta é a opção c: indefinida.
Agora, eu conheço o conceito de pontos de sequência em C ++. O comportamento é indefinido para a seguinte declaração:
int i = 0;
i += i++ + i++;
mas de acordo com meu entendimento para a declaração cout << a++ << a
, o ostream.operator<<()
seria chamado duas vezes, primeiro com ostream.operator<<(a++)
e depois ostream.operator<<(a)
.
Eu também verifiquei o resultado no compilador VS2010 e sua saída também é '01'.
10
, seria01
ou00
. (c++
sempre avaliará o valorc
obtido antes de ser incrementado). E mesmo que não fosse indefinido, ainda seria terrivelmente confuso.Respostas:
Você pode pensar em:
Como:
C ++ garante que todos os efeitos colaterais de avaliações anteriores foram executados em pontos de sequência . Não há pontos de sequência entre a avaliação dos argumentos da função, o que significa que o argumento
a
pode ser avaliado antesstd::operator<<(std::cout, a++)
ou depois do argumento . Portanto, o resultado do acima é indefinido.Atualização C ++ 17
No C ++ 17, as regras foram atualizadas. Em particular:
O que significa que requer o código para produzir o resultado
b
, que produz01
.Consulte P0145R3 Refining Expression Evaluation Order para Idiomatic C ++ para obter mais detalhes.
fonte
c
tem tipoint
,operator<<
aqui estão as funções-membro.operator<<
é uma função membro ou uma função autônoma não afeta os pontos de sequência.So the result of the above is undefined.
Sua explicação é válida apenas para não especificado , não para indefinido . JamesKanze explicou como ficou ainda mais indefinido em sua resposta .Tecnicamente, no geral, esse é o comportamento indefinido .
Mas, existem dois aspectos importantes para a resposta.
A declaração do código:
é avaliado como:
O padrão não define a ordem de avaliação dos argumentos para uma função.
Ou então:
std::operator<<(std::cout, a++)
é avaliado primeiro oua
é avaliado primeiro ouEste pedido não é especificado [Ref 1] de acordo com o padrão.
[Ref 1] C ++ 03 5.2.2 Chamada de função
Para 8
Além disso, não há ponto de sequência entre a avaliação de argumentos para uma função, mas um ponto de sequência existe apenas após a avaliação de todos os argumentos [Ref 2] .
[Ref 2] C ++ 03 1.9 Execução do programa [introdução.execução]:
Parágrafo 17:
Observe que, aqui o valor de
c
está sendo acessado mais de uma vez sem um ponto de sequência interveniente, em relação a isso o padrão diz:[Ref 3] C ++ 03 5 Expressões [expr]:
Para 4:
O código se modifica
c
mais de uma vez sem ponto de sequência intermediário e não está sendo acessado para determinar o valor do objeto armazenado. Esta é uma violação clara da cláusula acima e, portanto, o resultado conforme exigido pela norma é Comportamento indefinido [Ref 3] .fonte
Os pontos de sequência definem apenas uma ordem parcial . No seu caso, você tem (assim que a resolução da sobrecarga for concluída):
Há um ponto de sequência entre
a++
e a primeira chamada parastd::ostream::operator<<
e há um ponto de sequência entre a segundaa
e a segunda chamada parastd::ostream::operator<<
, mas não há ponto de sequência entrea++
ea
; as únicas restrições de ordenação são quea++
sejam totalmente avaliadas (incluindo efeitos colaterais) antes da primeira chamada paraoperator<<
e que a segundaa
seja totalmente avaliada antes da segunda chamada paraoperator<<
. (Existem também restrições de ordem causual: a segunda chamada paraoperator<<
não pode preceder a primeira, uma vez que requer os resultados do primeiro como um argumento.) §5 / 4 (C ++ 03) afirma:Uma das ordenações permitidos de sua expressão é
a++
,a
primeiro chamada paraoperator<<
, segunda chamada paraoperator<<
; isso modifica o valor armazenado dea
(a++
) e acessa-o de outra forma que não para determinar o novo valor (o segundoa
), o comportamento é indefinido.fonte
c
fosse um tipo de usuário com um usuário definido++
, em vez deint
, os resultados seriam não especificados, mas não haveria comportamento indefinido.c
emfoo(foo(bar(c)), c)
? Há um ponto de sequência quando as funções são chamadas e quando retornam, mas não há nenhuma chamada de função necessária entre as avaliações dos doisc
.c
fosse um UDT, os operadores sobrecarregados seriam chamadas de função e introduziriam um ponto de sequência, de modo que o comportamento não seria indefinido. Mas ainda não seria especificado se a subexpressãoc
foi avaliada antes ou depoisc++
, portanto, se você obteve a versão incrementada ou não, não seria especificado (e, em teoria, não teria que ser o mesmo todas as vezes).c
ec++
, portanto, as duas podem ocorrer em qualquer ordem. Quanto aos pontos e vírgulas ... Eles só causam um ponto de sequência na medida em que são expressões completas. Outros pontos de sequência importantes são a chamada de função:f(c++)
verá o incrementadoc
emf
e o operador vírgula&&
,||
e?:
também causará os pontos de sequência.A resposta correta é questionar a pergunta. A afirmação é inaceitável porque o leitor não consegue ver uma resposta clara. Outra maneira de ver isso é que introduzimos os efeitos colaterais (c ++) que tornam a declaração muito mais difícil de interpretar. O código conciso é ótimo, desde que seu significado seja claro.
fonte