Qual è l'effetto di questo incarico (qualunque sia la lingua)?

4

Penso che il mio libro ( Linguaggi di programmazione: principi e paradigmi ) sia sbagliato. a è un vettore, assumere un linguaggio simile 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.

Per me ha l'effetto opposto, cioè l'assegnazione del valore di a[2] + 1 a a[1] .

Poiché il componente della mano sinistra viene valutato per primo, quindi f restituisce 1 (perché b è 0 ), quindi a[1] . Quindi viene valutato il componente della mano destra, f restituisce 2 (perché ora b è 1 ), quindi a[2] + 1 .

Ho ragione e il libro è sbagliato? Sfortunatamente non ci sono errori ...

    
posta user34295 14.02.2013 - 17:05
fonte

5 risposte

20

Hai scritto "assumi un linguaggio simile alla C". Solo come C-like dovrebbe essere?

Per prima cosa, sembra che la logica dell'autore sia indietro:

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.

In effetti, ha questo effetto se la valutazione del LHS segue la valutazione del RHS.

Alcuni linguaggi "simili a C" (nel senso che la loro sintassi assomiglia a quelli di C) definiscono l'ordine di valutazione delle espressioni. (Penso che Java faccia questo.) In un tale linguaggio, la semantica del codice dipenderebbe esattamente da come il linguaggio definisce l'ordine di valutazione. Se l'ordine è da sinistra a destra, la chiamata di funzione sul LHS (lato sinistro) del compito verrà valutata per prima.

In C stesso (e in C ++), l'ordine di valutazione è non specificato . Le due chiamate di funzione potrebbero essere valutate in entrambi gli ordini, a seconda del capriccio del compilatore (l'ottimizzazione può cambiare questo, quindi, in linea di principio, può la fase lunare). Non credo che il comportamento di questo particolare codice sia in realtà indefinito, il che significherebbe che la lingua dice niente su come si comporta, ma un codice simile come:

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

ha un comportamento indefinito, perché i viene modificato due volte senza alcun punto di sequenza intermedio. In C, quanto sopra potrebbe valutare il LHS i++ prima di quello giusto, o viceversa, ma quelle non sono le uniche possibilità. Un compilatore C è libero di assumere che il tuo programma non modifichi i due volte tra i punti di sequenza. Questo particolare esempio sembra facile da rilevare per il compilatore, ma in casi più complessi può essere difficile o impossibile farlo.

Ma la vera risposta alla domanda è: Non scrivere codice del genere.

In realtà non è necessario avere due chiamate di funzione in una singola istruzione, in cui i risultati dipendono dall'ordine in cui vengono valutati. Separare le chiamate in istruzioni distinte, ad esempio:

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

E nella vita reale, vuoi nomi migliori di x , y , f e a - e f() probabilmente non dovrebbe restituire risultati diversi su chiamate successive, o dovrebbe essere molto chiaro perché ha bisogno di farlo.

Scrivi il tuo codice in modo che domande come questa non si presentino in primo luogo.

    
risposta data 14.02.2013 - 17:40
fonte
7

L'ordine di valutazione è solitamente indefinito, e quindi può andare in entrambi i modi a seconda del compilatore, anche se alcuni linguaggi pedanti potrebbero definirlo. Ecco perché la maggior parte delle volte quando vedi un esempio del genere, è per avvisarti di non farlo.

Se il compilatore segue rigorosamente l'ordine richiesto dall'indice, il libro ha ragione, perché devi leggere il valore, quindi eseguire l'aggiunta, quindi eseguire l'assegnazione. Tuttavia, non avere per calcolare gli indici di array nello stesso ordine in cui vengono utilizzati, e alcune ottimizzazioni potrebbero trarre vantaggio da questo fatto.

Consentitemi di dimostrare con uno pseudocodice di rappresentazione intermedia. Il primo passaggio di un compilatore di solito produrrà qualcosa del genere:

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

Linea 1 deve venire prima della linea 2. Linea 2 deve venire prima della linea 3. Linea 3 deve venire prima della linea 5. Linea 4 deve venire prima della riga 5. A parte questo, l'ordine di esecuzione non è solitamente definito dalla lingua, quindi i compilatori faranno tutto ciò che è più efficiente o più facile da implementare. È perfettamente permesso di finire così:

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

Un compilatore potrebbe farlo in questo modo se le ultime tre linee possono essere combinate in un'unica istruzione di assemblaggio. Poiché la destinazione viene spesso specificata per prima in un'istruzione di assemblaggio, potrebbe essere opportuno calcolare prima l'indice di destinazione, ma non avere per farlo in quell'ordine.

Un altro compilatore potrebbe non avere così tanti registri con cui lavorare, o avere un set di istruzioni più piccolo, in modo da poter calcolare prima l'indice sorgente per poter riutilizzare il registro temp1 .

Le lingue di solito lasciano intenzionalmente non definito per consentire alle implementazioni del compilatore di fare tutto ciò che è più efficiente.

    
risposta data 14.02.2013 - 17:18
fonte
2

Penso che la tua interpretazione sia corretta. Se l'autore del libro ha un indirizzo e-mail, potresti contattarlo.

Non ho letto il libro, ma immagino che il paragrafo successivo indicherà che è pericoloso fare affidamento su tali effetti collaterali, poiché rendono il codice difficile da leggere e porta a bug difficili. Quod erat demonstrandum , direi. : -)

    
risposta data 14.02.2013 - 17:15
fonte
1

La tua linea di codice è equivalente a

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

Il libro potrebbe averlo retrocesso, dicendo che qualcosa accade quando la sinistra (x) viene prima valutata, ma in realtà ciò accade quando il diritto (y) viene valutato per primo, ma questo non è quasi certamente il punto.

Il punto è che non sai se x o y saranno valutati per primi. Potresti sentire che "prima di calcolare il valore (lato destro), il compilatore dovrebbe calcolare la l -valore (lato sinistro) "ma non hai scritto tutti i compilatori sul pianeta. Alcuni scrittori di compilatori potrebbero voler andare da sinistra a destra, alcuni da destra a sinistra, alcuni secondo l'ordine di cui avranno bisogno: è necessario prima di tutto, per ottenere un valore da aggiungere 1 a, e solo allora hai bisogno di x per capire dove mettere il valore - e sono tutti corretti perché questa particolare decisione è lasciata allo scrittore del compilatore e non devi fare affidamento su di esso, mai.

    
risposta data 14.02.2013 - 17:35
fonte
0

Hai ragione. La prima chiamata a f () valuterà a 1, quelle successive a 2. Se l'operando di sinistra dell'assegnazione viene effettivamente valutato per primo (che è abbastanza discutibile), allora l'assegnazione restituisce:

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

Questo, tuttavia, presuppone anche che il valore di b non venga mai modificato prima di chiamare questa linea, e anche che f() non venga mai chiamato prima di questa riga.

Nota anche che il parametro n in f() non viene mai usato, quindi presumo che sia abbastanza probabile che ci siano alcuni errori di battitura lì.

Sarebbe interessante sapere quale punto l'autore sta cercando di fare qui.

    
risposta data 14.02.2013 - 17:15
fonte

Leggi altre domande sui tag