2016-01-06 11 views
11

ho letto here che c'è un punto sequenza:Punti sequenza in printf

Dopo l'azione associata formato di conversione ingresso/uscita specificatore. Ad esempio, nell'espressione printf("foo %n %d", &a, 42), è presente un punto di sequenza dopo la valutazione dello %n prima della stampa 42.

Tuttavia, quando ho eseguito this code:

int your_function(int a, int b) { 
    return a - b; 
} 

int main(void) { 
    int i = 10; 

    printf("%d - %d - %d\n", i, your_function(++i, ++i), i); 
} 

Invece di quello che mi aspetto ottengo:

12 - 0 - 12

il che significa che non vi era non un punto di sequenza creato per lo specificatore del formato di conversione. È http://en.wikipedia.org sbagliato, o ho appena frainteso qualcosa, o è gcc non-compatibile in questo caso (incidentalmente Visual Studio 2015 produce lo stesso risultato inaspettato)?

EDIT:

ho capito che l'ordine gli argomenti your_function vengono valutati e assegnati ai parametri è indefinito. Non sto chiedendo perché il mio medio termine è 0. Mi chiedo perché gli altri due termini sono entrambi 12.

+7

Secondo [questo] (http://stackoverflow.com/questions/ 4176328/undefined-behavior-and-sequence-points) è un comportamento non definito. Credo anche che 'your_function (++ i, ++ i)' sia anch'esso un comportamento indefinito. – NathanOliver

+3

'your_function (++ i, ++ i)' è chiaramente UB. –

+0

I punti di sequenza descritti dalla virgola standard rientrano nel corpo della funzione 'printf()' dopo che è stato chiamato. Hai un estremo comportamento indefinito nella sequenza chiamante prima che venga chiamata la funzione 'printf()' - il che significa che qualsiasi risultato è accettabile (incluso quello che hai ottenuto). –

risposta

6

Poiché questa domanda è stata posta a causa di una discussione commento a base here, fornirò qualche contesto:

primo commento: L'ordine delle operazioni è non garantito per essere l'ordine in che si passano gli argomenti alla funzione. Alcune persone (erroneamente) presumono che gli argomenti saranno valutati da destra a sinistra, ma secondo lo standard, il comportamento non è definito.

Il PO accetta e comprende questo. Non ha senso ripetere che il your_function(++i, ++i) è UB.

In risposta a quel commento: Grazie per il tuo commento vedo che printf possono essere valutati in qualsiasi ordine, ma ho capito che per essere a causa printf argomenti sono parte di un va_list. Stai dicendo che gli argomenti di qualsiasi funzione sono eseguiti in un ordine arbitrario?

OP chiedere chiarimenti, così ho elaborato un po ':

Seconda commento: Sì, questo è esattamente quello che sto dicendo. anche chiamando lo int your_function(int a, int b) { return a - b; } non garantisce che le espressioni passate vengano valutate da sinistra a destra.Non esiste un punto di sequenza (un punto in cui vengono eseguiti tutti gli effetti collaterali delle precedenti valutazioni). Prendi lo this example. La chiamata nidificata è un punto di sequenza, quindi la chiamata esterna passa i+1 (13) e il valore di ritorno della chiamata interna (indefinita, in questo caso -1 perché i++, i valuta a 12, 13 a quanto pare), ma non c'è garantire che questo sia sempre il caso

Questo ha reso abbastanza chiaro che questi tipi di costrutti attivano UB per tutte le funzioni.


Wikipedia confusione

quotazioni op Questa:

Dopo l'azione associata formato di conversione ingresso/uscita specificatore. Ad esempio, nel printf espressione ("foo% n% d", & a, 42), v'è un punto sequenza dopo l'% n viene valutata prima della stampa 42.

viene quindi applicato al suo frammento (prinf("%d - %d - %d\n", i, your_function(++i, ++i), i);) aspettando che gli specificatori di formato servano come punti di sequenza.
A cosa viene riferito dicendo "" identificatore del formato di conversione input/output " è l'identificatore %n. L'argomento corrispondente deve essere essere un puntatore a un numero intero senza segno e verrà assegnato il numero di caratteri stampati fino a quel momento. Naturalmente, è necessario valutare %n prima di stampare il resto degli argomenti. Tuttavia, utilizzando il puntatore passato per %n in altri argomenti è ancora pericoloso: è non UB (beh, non lo è, ma può essere):

printf("Foo %n %*s\n", &a, 100-a, "Bar");//DANGER!! 

C'è un punto sequenza prima viene chiamata la funzione, quindi l'espressione 100-asarà prima di %n ha impostato &a sul valore corretto. Se a non è inizializzato, allora 100-a è UB. Se a viene inizializzato su 0, ad esempio, il risultato dell'espressione sarà pari a 100. Nel complesso, tuttavia, questo tipo di codice richiede praticamente problemi. Trattarlo come pessima pratica, o peggio ...
Basta guardare l'output generato da una di queste affermazioni:

unsigned int a = 90; 
printf("%u %n %*s\n",a, &a, 10, "Bar");//90   Bar 
printf("%u\n", a);//3 
printf("Foo %u %n %*s\n",a, &a, 10-a, "Bar");//Foo 3  Bar < padding used: 10 - 3, not 10 - 6 
printf("%u\n", a);//6 

a come si può vedere, n ottiene riassegnato all'interno di printf , quindi non puoi usare il suo nuovo valore nella lista degli argomenti (perché c'è un punto di sequenza). Se si prevede di riassegnare "n" sul posto ", in sostanza si aspetta che C salti fuori dalla chiamata della funzione, valuti altri argomenti e torni indietro nella chiamata. Questo non è possibile. Se si dovesse cambiare unsigned int a = 90; a unsigned int a;, il comportamento non è definito.


Per quanto riguarda 's il 12

Ora, poiché l'OP leggere su punti di sequenza, si accorge, giustamente, che questa dichiarazione:

printf("%d - %d - %d\n", i, your_function(++i, ++i), i); 

è leggermente diversa: your_function(++i, ++i)è un punto di sequenza e garanzie che i verrà incrementato due volte. Questa chiamata di funzione è un punto di sequenza perché:

Prima di immettere una funzione in una chiamata di funzione. L'ordine in cui vengono valutati gli argomenti non è specificato, ma questo punto sequenza significa che tutti i loro effetti collaterali sono completi prima di inserire la funzione

Ciò significa che, prima printf chiamata, your_functionha a essere chiamato (poiché il suo valore restituito è uno degli argomenti per la chiamata printf) e i verrà incrementato due volte.
Questo potrebbe spiegare l'uscita essendo "12-0 - 12", ma è garantito per essere l'uscita?

No

Tecnicamente, anche se la maggior parte dei compilatori valuteranno la chiamata your_function(++i, ++i); prima, lo standard permetterebbe un compilatore di valutare gli argomenti passati al sprintf da sinistra a destra (l'ordine non è specificato, dopo tutto). Quindi questo sarebbe un risultato altrettanto valida:

10 - 0 - 12 
//or even 
12 - 0 - 10 
//and 
10 - 0 - 10 
//technically, even this would be valid 
12 - 0 - 11 

Sebbene quest'ultima uscita è estremamente improbabile (sarebbe molto inefficiente)

11

Penso che tu abbia frainteso il testo sui punti di sequenza printf (SP). Sono in qualche modo un'anomalia, e solo con %n perché questo specificatore di formato ha effetti collaterali, e quegli effetti collaterali devono essere sequenziati.

In ogni caso, c'è un SP all'inizio dell'esecuzione di printf() e dopo la valutazione di tutti gli argomenti. Questi SP per la formattazione sono tutti dopo questo in modo che non influenzino il tuo problema.

Nel tuo esempio, gli usi di i sono tutti in argomenti di funzione e nessuno di essi è separato con punti di sequenza. Poiché modifichi il valore (due volte) e utilizzi il valore senza punti di sequenza intermedi, il tuo codice è UB.

Che regola la SP in printf significa è che questo codice è ben formata:

int x; 
printf("%d %n %d %n\n", 1, &x, 2, &x); 

anche se il valore di x viene modificato due volte.

Ma questo codice è UB:

int x = 1; 
printf("%d %d\n", x, ++x); 

NOTA: Ricordate che %n significa che il numero di caratteri scritti finora viene copiato l'intero puntato dall'argomento associato.

+0

Principalmente corretto, ma "nessuno di essi è separato con punti di sequenza" non è completamente accurato. Nello specifico, c'è un punto di sequenza dopo che gli argomenti di 'your_function()' (e 'function designator') sono stati valutati e prima che la funzione venga chiamata. Quindi, entrambi gli incrementi sono completi prima che venga chiamata quella funzione. Quello che non si può dire è quando gli altri argomenti su 'printf()' sono stati valutati w.r.t la chiamata a 'your_function()' - che non è definita, quindi i valori passati a 'printf()' non sono definiti. –

+0

@JonathanLeffler: Poiché tutti gli argomenti su 'printf()' e 'your_function()' possono essere valutati senza SP intermedio, direi che "nessuno di essi è separato con SP" è più o meno accurato (dovrei dire "sequenced" intead di "separated"?). L'SP che punti effettivamente esegue le chiamate a 'your_function()' e 'printf()'. – rodrigo

+0

Sorta. È disordinato. C'è, enfaticamente, un punto di sequenza dopo che gli argomenti di 'your_function()' sono stati valutati e prima che la funzione venga chiamata. Il terzo argomento per 'printf()', quindi, non può essere valutato fino a quando non viene restituita la chiamata a 'your_function()'; e quella chiamata deve quindi precedere la SP prima della chiamata a 'printf()'. Altrimenti, non esistono vincoli sull'ordine di valutazione degli argomenti a 'printf()'. In questa fase, queste minuzie non contano davvero: il comportamento generale è UB a causa dei due incrementi a 'i', e qualsiasi cosa può accadere come risultato di UB. –

4

L'arrivo a una risposta chiara a questa domanda è fortemente compromesso (anche impedito) dalle regole C sull'ordine di valutazione e UB.

Le regole specificate in ordine di valutazione sono indicati qui:

C99 sezione 6.7.9, P23: 23 Le valutazioni della lista di inizializzazione espressioni vengono indeterminately sequenziati con uno rispetto all'altro e quindi il l'ordine in cui si verificano effetti collaterali non è specificato.

E, this function call will exhibit undefined behavior:

your_function(++i, ++i) 

A causa di UB, insieme con le norme in materia di ordine di valutazione, previsioni accurate sui risultati attesi per il seguente:

printf("%d - %d - %d\n", i, your_function(++i, ++i), i); 

sono impossibile.

Modifica
... Non sto chiedendo il motivo per cui il mio termine medio è 0. sto chiedendo perché gli altri due termini sono entrambi 12.

v'è alcuna garanzia quale dei tre argomenti della funzione sopra è chiamato prima. (a causa delle regole del C sull'ordine di valutazione). E se la funzione centrale viene valutata per prima, allora a quel punto hai invocato Undefined Behavior. Chi può veramente dire perché gli altri due termini sono 12 ?. ?. Perché quello che succede a i quando viene valutato il secondo argomento è l'ipotesi di chiunque.

+0

Ho modificato la domanda per fornire chiarimenti: "Non sto chiedendo perché il mio medio termine è 0. Mi sto chiedendo perché gli altri due termini siano entrambi 12." –

+0

@JonathanMee - A causa delle regole sull'ordine di valutazione, l'argomento centrale potrebbe essere valutato per primo, risultando in UB per almeno se stesso ('your_function()'), e possibilmente avendo un effetto downstream sui risultati successivi. Quindi non potrebbe essere possibile che UB _bleeds through_ all'intera funzione: 'printf ("% d -% d -% d \ n ", i, tuo_funzione (++ i, ++ i), i);'? – ryyker

+0

La tua affermazione "letteralmente tutto può succedere" è falsa. Lì * è * un punto di sequenza sul ritorno di una funzione, quindi dopo aver chiamato 'your_function',' i' * sarà * 12. Questa domanda riguardava l'affermazione su http: //en.wikipedia.org significava che potevo dipendere dal fatto che gli argomenti di 'printf' venivano chiamati in ordine. –