Gli operatori logici di corto circuito sono obbligati? E ordine di valutazione?


140

Lo standard ANSI impone agli operatori logici di essere cortocircuitati, in C o C ++?

Sono confuso perché ricordo il libro di K&R che dice che il tuo codice non dovrebbe dipendere da queste operazioni in corto circuito, perché potrebbero non esserlo. Qualcuno potrebbe indicare dove nello standard si dice che le operazioni logiche sono sempre in corto circuito? Sono principalmente interessato al C ++, una risposta anche per il C sarebbe ottima.

Ricordo anche di aver letto (non ricordo dove) che l'ordine di valutazione non è strettamente definito, quindi il codice non dovrebbe dipendere o assumere che le funzioni all'interno di un'espressione vengano eseguite in un ordine specifico: entro la fine di un'istruzione tutte le funzioni a cui viene fatto riferimento sarà stato chiamato, ma il compilatore ha la libertà di selezionare l'ordine più efficiente.

Lo standard indica l'ordine di valutazione di questa espressione?

if( functionA() && functionB() && functionC() ) cout<<"Hello world";

12
Attenzione: è vero per i tipi di POD. Ma se sovraccarichi l'operatore && o l'operatore || per una particolare classe NON sono ripetizione NON scorciatoia. Questo è il motivo per cui si consiglia di NON definire questi operatori per le proprie classi.
Martin York,

Ho ridefinito questi operatori un po 'di tempo fa, quando ho creato una classe che avrebbe eseguito alcune operazioni di algebra booleana di base. Probabilmente dovrebbe mettere un commento di avvertimento "questo distrugge il corto circuito e la valutazione sinistra-destra!" nel caso lo dimentichi. Inoltre ha sovraccaricato * / + e li ha resi i loro sinonimi :-)
Joe Pineda

Avere chiamate di funzione in un blocco if non è una buona pratica di programmazione. Dichiarare sempre una variabile che contenga il valore restituito dal metodo e utilizzarlo nel blocco if.
SR Chaitanya,

6
@SRChaitanya Non è corretto. Ciò che tu descrivi arbitrariamente come cattiva pratica viene fatto continuamente, specialmente con funzioni che ritornano booleane, come qui.
Marchese di Lorne,

Risposte:


154

Sì, sono richiesti cortocircuiti e ordini di valutazione per gli operatori ||e&& negli standard C e C ++.

Lo standard C ++ dice (ci dovrebbe essere una clausola equivalente nello standard C):

1.9.18

Nella valutazione delle seguenti espressioni

a && b
a || b
a ? b : c
a , b

usando il significato incorporato degli operatori in queste espressioni, c'è un punto sequenza dopo la valutazione della prima espressione (12).

In C ++ c'è una trappola extra: il corto circuito NON si applica ai tipi che sovraccaricano gli operatori ||e &&.

Nota 12: Gli operatori indicati in questo paragrafo sono gli operatori integrati, come descritto nella clausola 5. Quando uno di questi operatori è sovraccarico (clausola 13) in un contesto valido, designando così una funzione di operatore definita dall'utente, l'espressione designa una chiamata di funzione e gli operandi formano un elenco di argomenti, senza un punto di sequenza implicito tra di loro.

Di solito non è consigliabile sovraccaricare questi operatori in C ++ a meno che tu non abbia un requisito molto specifico. Puoi farlo, ma potrebbe interrompere il comportamento previsto nel codice di altre persone, specialmente se questi operatori vengono utilizzati indirettamente tramite modelli di istanza con il tipo che sovraccarica questi operatori.


3
Non sapevo che i cortocircuiti non si applicassero alle operazioni logiche sovraccariche, questo è intestino. Potete per favore aggiungere un riferimento allo standard o una fonte? Non ti sto diffidando, voglio solo saperne di più su questo.
Joe Pineda,

4
sì, è logico. sta fungendo da argomento per l'operatore && (a, b). è la sua implementazione che dice cosa succede.
Johannes Schaub -

10
litb: non è possibile passare b all'operatore && (a, b) senza valutarlo. E non è possibile annullare la valutazione di b perché il compilatore non può garantire che non vi siano effetti collaterali.
jmucchiello,

2
Lo trovo triste. Ci avrei pensato, se avessi ridefinito gli operatori && e || e sono ancora del tutto deterministici , il compilatore lo rileverebbe e manterrebbe la loro valutazione in corto circuito: dopo tutto, l'ordine è irrilevante, e non garantiscono effetti collaterali!
Joe Pineda,

2
@Joe: ma il valore di ritorno e gli argomenti dell'operatore possono cambiare da booleano a qualcos'altro. Ho usato per implementare la logica "speciale" con TRE valori ("vero", "falso" e "sconosciuto"). Il valore di ritorno è deterministico, ma il comportamento in corto circuito non è appropriato.
Alex B

70

La valutazione del corto circuito e l'ordine di valutazione sono uno standard semantico obbligatorio in C e C ++.

Se così non fosse, un codice come questo non sarebbe un linguaggio comune

   char* pChar = 0;
   // some actions which may or may not set pChar to something
   if ((pChar != 0) && (*pChar != '\0')) {
      // do something useful

   }

Sezione 6.5.13 Operatore logico AND della specifica C99 (collegamento PDF) dice

(4). A differenza dell'operatore binario bit a bit, l'operatore && garantisce una valutazione da sinistra a destra; esiste un punto sequenza dopo la valutazione del primo operando. Se il primo operando confronta uguale a 0, il secondo operando non viene valutato.

Analogamente, la sezione 6.5.14 OR logico operatore dice

(4) A differenza del bit per bit | operatore, il || l'operatore garantisce una valutazione da sinistra a destra; c'è un punto sequenza dopo la valutazione del primo operando. Se il primo operando confronta in modo diverso da 0, il secondo operando non viene valutato.

Formulazioni simili sono disponibili negli standard C ++, consultare la sezione 5.14 di questa bozza . Come notano gli ispettori in un'altra risposta, se si sostituisce && o ||, entrambi gli operandi devono essere valutati in quanto diventa una normale chiamata di funzione.


Ah, quello che stavo cercando! OK, quindi sia l'ordine di valutazione che il corto circuito sono obbligatori secondo ANSI-C 99! Mi piacerebbe davvero vedere il riferimento equivalente per ANSI-C ++, anche se sono quasi il 99% deve essere lo stesso.
Joe Pineda,

Difficile trovare un buon collegamento gratuito per gli standard C ++, ho collegato a una bozza di copia che ho trovato con alcuni googling.
Paul Dixon,

Vero per i tipi di POD. Ma se sovraccarichi l'operatore && o l'operatore || questi non sono collegamenti.
Martin York,

1
Sì, è interessante notare che per Bool avrai sempre un ordine di valutazione e un comportamento in corto circuito garantiti. perché non è possibile sovraccaricare l'operatore && per due tipi predefiniti. è necessario almeno un tipo definito dall'utente negli operandi per comportarsi in modo diverso.
Johannes Schaub - lett.

Vorrei poter accettare sia Dama che questa risposta. Dato che sono principalmente interessato al C ++, sto accettando l'altro, anche se devo ammettere che anche questo è fantastico! Grazie mille!
Joe Pineda,

19

Sì, impone questo (sia l'ordine di valutazione che il corto circuito). Nel tuo esempio se tutte le funzioni ritornano vere, l'ordine delle chiamate è rigorosamente dalla funzione A, quindi dalla funzione B e quindi dalla funzione C. Usato per questo tipo

if(ptr && ptr->value) { 
    ...
}

Lo stesso per l'operatore virgola:

// calls a, then b and evaluates to the value returned by b
// which is used to initialize c
int c = (a(), b()); 

Si dice tra destra e sinistra operando di &&, ||, ,e tra la prima e seconda / terza operando ?:(operatore condizionale) è un "punto sequenza". Tutti gli effetti collaterali vengono valutati completamente prima di quel punto. Quindi, questo è sicuro:

int a = 0;
int b = (a++, a); // b initialized with 1, and a is 1

Nota che l'operatore virgola non deve essere confuso con la virgola sintattica usata per separare le cose:

// order of calls to a and b is unspecified!
function(a(), b());

Lo standard C ++ dice in 5.14/1:

L'operatore && raggruppa da sinistra a destra. Gli operandi vengono entrambi implicitamente convertiti in tipo bool (clausola 4). Il risultato è vero se entrambi gli operandi sono veri e falsi altrimenti. A differenza di &, && garantisce la valutazione da sinistra a destra: il secondo operando non viene valutato se il primo operando è falso.

E in 5.15/1:

Il || gruppi di operatori da sinistra a destra. Gli operandi sono entrambi implicitamente convertiti in bool (clausola 4). Restituisce vero se uno dei suoi operandi è vero e falso altrimenti. A differenza di |, || garantisce la valutazione da sinistra a destra; inoltre, il secondo operando non viene valutato se il primo operando viene valutato come vero.

Dice per entrambi accanto a quelli:

Il risultato è un bool. Tutti gli effetti collaterali della prima espressione tranne la distruzione dei provvisori (12.2) si verificano prima che la seconda espressione venga valutata.

Inoltre, 1.9/18afferma

Nella valutazione di ciascuna delle espressioni

  • a && b
  • a || b
  • a ? b : C
  • a , b

usando il significato incorporato degli operatori in queste espressioni (5.14, 5.15, 5.16, 5.18), c'è un punto di sequenza dopo la valutazione della prima espressione.


10

Direttamente dal buon vecchio K&R:

C garantisce che &&e ||sono valutati da sinistra a destra - vedremo presto casi in cui questo è importante.


3
K&R 2nd edition p40. "Le espressioni collegate da && o || vengono valutate da sinistra a destra e la valutazione si interrompe non appena si conosce la verità o la falsità del risultato. La maggior parte dei programmi C si basa su queste proprietà." Non riesco a trovare il tuo testo citato da nessuna parte nel libro. È dalla 1a edizione estremamente obsoleta? Per favore, chiarisci dove hai trovato questo testo.
Lundin,

1
Ok risulta che stai citando questo antico tutorial . È del 1974 ed è estremamente irrilevante.
Lundin,

6

Stai molto attento.

Per i tipi fondamentali si tratta di operatori di scelta rapida.

Ma se si definiscono questi operatori per la propria classe o tipo di enumerazione, questi non sono collegamenti. A causa di questa differenza semantica nel loro utilizzo in queste diverse circostanze, si consiglia di non definire questi operatori.

Per i tipi fondamentali operator &&e operator ||per l'ordine di valutazione è da sinistra a destra (altrimenti il ​​taglio corto sarebbe difficile :-) Ma per gli operatori sovraccarichi che si definiscono, questi sono fondamentalmente zucchero sintattico per definire un metodo e quindi l'ordine di valutazione dei parametri è non definito.


1
Il sovraccarico dell'operatore non ha nulla a che fare con il tipo POD o no. Per definire una funzione operatore, almeno uno degli argomenti deve essere una classe (o struttura o unione) o un enum o un riferimento a uno di questi. Essere POD significa che puoi usare memcpy su di esso.
Derek Ledbetter,

Ed è quello che stavo dicendo. Se sovraccarichi && per la tua classe, allora è davvero solo una chiamata di metodo. Pertanto, non è possibile fare affidamento sull'ordine di valutazione dei parametri. Ovviamente non puoi sovraccaricare && per i tipi POD.
Martin York,

3
Si sta utilizzando il termine "tipi POD" in modo errato. Puoi sovraccaricare && per qualsiasi struttura, classe, unione o enum, POD o meno. Non è possibile sovraccaricare && se entrambi i lati sono tipi o puntatori numerici.
Derek Ledbetter,

Stavo usando POD come (char / int / float ecc.) Non un POD aggravato (che è quello di cui stai parlando) e di solito viene indicato separatamente o più esplicitamente perché non è un tipo incorporato.
Martin York,

2
Quindi intendevi "tipi fondamentali" ma hai scritto "tipi POD"?
Öö Tiib,

0

La tua domanda si riduce alla precedenza e all'associatività dell'operatore C ++ . Fondamentalmente, nelle espressioni con più operatori e senza parentesi, il compilatore costruisce l'albero delle espressioni seguendo queste regole.

Per la precedenza, quando hai qualcosa del genere A op1 B op2 C, puoi raggruppare le cose come una (A op1 B) op2 Co A op1 (B op2 C). Se op1ha una precedenza maggiore di op2, otterrai la prima espressione. Altrimenti, otterrai il secondo.

Per associatività, quando si ha qualcosa di simile A op B op C, è possibile raggruppare nuovamente i thin come (A op B) op Co A op (B op C). Se opha lasciato l'associatività, finiamo con la prima espressione. Se ha la giusta associatività, finiamo con la seconda. Questo funziona anche per gli operatori allo stesso livello di precedenza.

In questo caso particolare, &&ha una precedenza maggiore di ||, quindi l'espressione verrà valutata come(a != "" && it == seqMap.end()) || isEven .

L'ordine stesso è "da sinistra a destra" nella forma dell'albero delle espressioni. Quindi valuteremo prima a != "" && it == seqMap.end(). Se è vero, l'intera espressione è vera, altrimenti andiamo a isEven. Ovviamente la procedura si ripete in modo ricorsivo all'interno della sottoespressione di sinistra.


Idee interessanti, ma il concetto di precedenza ha le sue radici nella notazione matematica. La stessa cosa accade in a*b + c, dove *ha una precedenza più alta di +.

Ancora più interessante / oscuro, per un'espressione senza paragoni A1 op1 A2 op2 ... opn-1 An, in cui tutti gli operatori hanno la stessa precedenza, il numero di alberi di espressioni binarie che potremmo formare è dato dai cosiddetti numeri catalani . Per grandi n, questi crescono estremamente velocemente. d


Tutto ciò è corretto, ma riguarda la precedenza e l'associatività dell'operatore, non l'ordine di valutazione e la riduzione delle richieste. Quelle sono cose diverse.
Thomas Padron-McCarthy,

0

Se ti fidi di Wikipedia:

[ &&e ||] sono semanticamente distinti dagli operatori bit-saggi & e | perché non valuteranno mai l'operando giusto se il risultato può essere determinato solo da sinistra

C (linguaggio di programmazione)


11
Perché fidarsi di wiki quando abbiamo uno standard!
Martin York,


Questo è vero fino in fondo, ma incompleto, poiché gli operatori sovraccarichi in C ++ non sono in cortocircuito.
Thomas Padron-McCarthy,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.