Qual é o efeito desta tarefa (seja qual for a língua)?

4

Acho que meu livro ( Linguagens de Programação: Princípios e Paradigmas ) é errado. a é um vetor, suponha uma linguagem semelhante a C:

b = 0;
a[f(3)] = a[f(3)] + 1;

int f(int n) {
    if(b == 0) {
        b = 1;

        return 1;
    }
    else return 2;
}

It has the effect of assigning the value of a[1] + 1 to a[2] whenever the evaluation of the left-hand component of the assignment precedes the evaluation of the right-hand one.

Para mim, tem o efeito oposto, que é atribuir o valor de a[2] + 1 a a[1] .

Como o componente à esquerda é avaliado primeiro, então f retorna 1 (porque b é 0 ), portanto a[1] . Em seguida, o componente direito é avaliado, f retorna 2 (porque agora b é 1 ), portanto a[2] + 1 .

Estou certo e o livro está errado? Infelizmente não há errata ...

    
por user34295 14.02.2013 / 17:05
fonte

5 respostas

20

Você escreveu "assumir uma linguagem semelhante a C". Apenas como deve ser o estilo C?

Primeiro, parece que a lógica do autor está para trás:

It has the effect of assigning the value of a[1] + 1 to a[2] whenever the evaluation of the left-hand component of the assignment precedes the evaluation of the right-hand one.

De fato, tem esse efeito se a avaliação do LHS seguir a avaliação do RHS.

Algumas linguagens "C-like" (no sentido de que sua sintaxe se parece com a de C) definem a ordem de avaliação das expressões. (Eu acho que Java faz isso.) Em tal linguagem, a semântica do código dependeria exatamente como a linguagem define a ordem de avaliação. Se a ordem for da esquerda para a direita, a chamada de função no LHS (lado esquerdo) da atribuição será avaliada primeiro.

No próprio C (e em C ++), a ordem de avaliação é não especificada . As duas chamadas de função podem ser avaliadas em qualquer ordem, dependendo do capricho do compilador (a otimização pode mudar isso; assim, em princípio, pode a fase da lua). Eu não acredito que o comportamento deste código em particular esteja realmente indefinido, o que significaria que a linguagem diz nada sobre como ele se comporta, mas código semelhante como:

a[i++] = a[i++] + 1;

tem um comportamento indefinido, porque i é modificado duas vezes sem nenhum ponto de sequência . Em C, o acima poderia avaliar o LHS i++ antes do caminho certo, ou vice-versa - mas essas não são as únicas possibilidades. Um compilador C está livre para assumir que seu programa não modifica i duas vezes entre os pontos da sequência. Esse exemplo específico parece fácil para o compilador detectar, mas em casos mais complexos pode ser difícil ou impossível fazê-lo.

Mas a resposta real para a pergunta é: Não escreva código assim.

Você realmente não precisa ter duas chamadas de função em uma única instrução, em que os resultados dependem da ordem em que são avaliados. Separe as chamadas em declarações distintas, por exemplo:

int x = f(3);
int y = f(3);
a[x] = a[y] + 1;

E na vida real, você deseja nomes melhores que x , y , f e a - e f() provavelmente não deve retornar resultados diferentes em chamadas sucessivas, ou deve ser muito claro porque precisa fazê-lo.

Escreva o seu código para que questões como esta não surjam em primeiro lugar.

    
por 14.02.2013 / 17:40
fonte
7

A ordem de avaliação é geralmente indefinida e, portanto, pode ir de qualquer forma, dependendo do compilador, embora algumas linguagens pedantes possam defini-la. É por isso que na maioria das vezes quando você vê um exemplo como esse, é para avisá-lo a não fazer isso.

Se o compilador for estritamente pela ordem em que o índice é necessário, o livro está correto, porque você precisa ler o valor, depois executar a adição e fazer a atribuição. No entanto, você não tem para calcular os índices da matriz na mesma ordem em que são usados, e algumas otimizações podem tirar proveito desse fato.

Permitir que eu demonstre com algum pseudocódigo de representação intermediária. A primeira passagem de um compilador geralmente produzirá algo assim:

temp1 = f(3)
temp2 = a[temp1]
temp3 = temp2 + 2
temp4 = f(3)
a[temp4] = temp3

Linha 1 deve vir antes da linha 2. Linha 2 deve vir antes da linha 3. Linha 3 deve vir antes da linha 5. Linha 4 deve vir antes da linha 5. Além disso, a ordem de execução geralmente não é definida pela linguagem, então os compiladores farão o que for mais eficiente ou mais fácil de implementar. É perfeitamente permitido acabar assim:

temp4 = f(3)
temp1 = f(3)
temp2 = a[temp1]
temp3 = temp2 + 2
a[temp4] = temp3

Um compilador pode fazer isso se as três últimas linhas puderem ser combinadas em uma única instrução de montagem. Como o destino geralmente é especificado primeiro em uma instrução de montagem, pode fazer sentido calcular o índice de destino primeiro, mas você não precisa fazer para fazê-lo nessa ordem.

Outro compilador pode não ter tantos registros para trabalhar, ou ter um conjunto de instruções menor, então eles podem calcular primeiro o índice de origem para poder reutilizar o registro temp1 .

As linguagens normalmente deixam indefinido para permitir que as implementações do compilador façam o que for mais eficiente.

    
por 14.02.2013 / 17:18
fonte
2

Eu acho que sua interpretação está correta. Se o autor do livro tiver um endereço de e-mail, talvez você queira entrar em contato com ele.

Eu não li o livro, mas acho que o próximo parágrafo indicará que é perigoso confiar em tais efeitos colaterais, já que eles tornam o código difícil de ler e levam a erros difíceis. Quod erat demonstrandum , eu diria. : -)

    
por 14.02.2013 / 17:15
fonte
1

Sua linha de código é equivalente a

a[x] = a [y] + 1;

O livro pode tê-lo de trás para frente, dizendo que algo acontece quando o esquerdo (x) é avaliado primeiro, mas na verdade isso acontece quando o direito (y) é avaliado primeiro, mas isso certamente não é o ponto.

O ponto é que você não sabe se x ou y serão avaliados primeiro. Você pode sentir que "antes de calcular o valor (lado direito), o compilador deve computar -value (lado esquerdo) "mas você não escreveu todos os compiladores no planeta. Alguns escritores do compilador podem querer ir da esquerda para a direita, alguns da direita para a esquerda, alguns de acordo com a ordem que você precisa - você precisa primeiro, para obter um valor para adicionar 1, e só então você precisa x para descobrir onde colocar o valor - e eles estão todos corretos porque esta decisão em particular é deixada para o compilador e você não deve confiar nela, nunca.

    
por 14.02.2013 / 17:35
fonte
0

Você está correto. A primeira chamada para f () será avaliada como 1, as subsequentes serão avaliadas como 2. Se o operando à esquerda da atribuição for realmente avaliado primeiro (o que é discutível), a atribuição será avaliada como:

a[1] = a[2] + 1;

Isso, entretanto, também assume que o valor de b nunca é modificado antes que essa linha seja chamada, e também que f() nunca é chamado antes desta linha.

Observe também que o parâmetro n para f() nunca é usado, então eu suponho que é bem provável que haja algum tipo de erro lá.

Seria interessante saber que ponto o autor está tentando fazer aqui.

    
por 14.02.2013 / 17:15
fonte