Eu estava lendo sobre a ordem das violações de avaliação , e eles dão um exemplo que me intriga.
1) Se um efeito colateral em um objeto escalar não for seqüenciado em relação a outro efeito colateral no mesmo objeto escalar, o comportamento será indefinido.
// snip f(i = -1, i = -1); // undefined behavior
Nesse contexto, i
é um objeto escalar , o que aparentemente significa
Tipos aritméticos (3.9.1), tipos de enumeração, tipos de ponteiros, ponteiros para tipos de membros (3.9.2), std :: nullptr_t e versões qualificadas para cv desses tipos (3.9.3) são coletivamente chamados de tipos escalares.
Não vejo como a afirmação é ambígua nesse caso. Parece-me que, independentemente de o primeiro ou o segundo argumento ser avaliado primeiro, ele i
termina como -1
e os dois argumentos também -1
.
Alguém pode esclarecer?
ATUALIZAR
Eu realmente aprecio toda a discussão. Até agora, eu gosto muito da resposta do harmic, uma vez que expõe as armadilhas e os meandros da definição dessa declaração, apesar de quão simples ela parece à primeira vista. O @ acheong87 aponta alguns problemas que surgem ao usar referências, mas acho que isso é ortogonal ao aspecto dos efeitos colaterais não decorrentes desta questão.
RESUMO
Como essa pergunta recebeu muita atenção, resumirei os principais pontos / respostas. Primeiro, permita-me uma pequena digressão para apontar que "por que" pode ter significados intimamente relacionados, porém sutilmente diferentes, a saber "por qual causa ", "por qual motivo " e "com que finalidade ". Agruparei as respostas pelas quais esses significados de "por que" eles abordaram.
por que causa
A principal resposta aqui vem de Paul Draper , com Martin J contribuindo com uma resposta semelhante, mas não tão extensa. A resposta de Paul Draper se resume a
É um comportamento indefinido porque não está definido qual é o comportamento.
Em geral, a resposta é muito boa em termos de explicação do que o padrão C ++ diz. Ele também aborda alguns casos relacionados de UB, como f(++i, ++i);
e f(i=1, i=-1);
. No primeiro dos casos relacionados, não está claro se o primeiro argumento deve ser i+1
e o segundo i+2
ou vice-versa; no segundo, não está claro se i
deve ser 1 ou -1 após a chamada da função. Ambos os casos são UB porque se enquadram na seguinte regra:
Se um efeito colateral em um objeto escalar não for relacionado em relação a outro efeito colateral no mesmo objeto escalar, o comportamento será indefinido.
Portanto, f(i=-1, i=-1)
também é UB, uma vez que se enquadra na mesma regra, apesar de a intenção do programador ser (IMHO) óbvia e inequívoca.
Paul Draper também deixa explícito em sua conclusão que
Poderia ter sido um comportamento definido? Sim. Foi definido? Não.
o que nos leva à questão de "por que razão / propósito foi f(i=-1, i=-1)
deixado como comportamento indefinido?"
por que razão / propósito
Embora haja alguns descuidos (talvez descuidados) no padrão C ++, muitas omissões são bem fundamentadas e servem a um propósito específico. Embora eu esteja ciente de que o objetivo geralmente é "facilitar o trabalho do redator do compilador" ou "código mais rápido", eu estava interessado principalmente em saber se há um bom motivo para sair f(i=-1, i=-1)
como UB.
harmic e supercat fornecer os principais respostas que fornecem uma razão para a UB. Harmic aponta que um compilador otimizador que pode dividir as operações de atribuição ostensivamente atômicas em várias instruções da máquina e que pode intercalar ainda mais essas instruções para obter a velocidade ideal. Isso pode levar a resultados muito surpreendentes: i
acaba sendo -2 no cenário dele! Portanto, harmônico demonstra como atribuir o mesmo valor a uma variável mais de uma vez pode ter efeitos negativos se as operações não forem subsequentes.
O supercat fornece uma exposição relacionada das armadilhas de tentar f(i=-1, i=-1)
fazer o que parece que deveria fazer. Ele ressalta que, em algumas arquiteturas, há restrições rígidas contra várias gravações simultâneas no mesmo endereço de memória. Um compilador poderia ter dificuldade em entender isso se estivéssemos lidando com algo menos trivial do que f(i=-1, i=-1)
.
O davidf também fornece um exemplo de instruções de intercalação muito semelhantes às do harmônico.
Embora cada um dos exemplos de harmônicos, supercat e davidf seja um pouco artificial, juntos eles ainda servem para fornecer uma razão tangível pela qual f(i=-1, i=-1)
deve haver um comportamento indefinido.
Aceitei a resposta do harmic porque fazia o melhor trabalho para abordar todos os significados do porquê, mesmo que a resposta de Paul Draper tenha abordado melhor a parte "por que causa".
outras respostas
JohnB ressalta que, se considerarmos operadores de atribuição sobrecarregados (em vez de escalares simples), também podemos ter problemas.
f(i-1, i = -1)
ou algo parecido.
std::nullptr_t
e versões qualificadas para cv desses tipos (3.9.3) são coletivamente chamadas de tipos escalares . "