2015-05-21 10 views
5

In this thread la risposta più votata ha ricevuto molti voti e persino una taglia. Si propone il seguente algoritmo:Accesso alla stessa posizione di memoria due volte, UB o no?

void RemoveSpaces(char* source) 
{ 
    char* i = source; 
    char* j = source; 
    while(*j != 0) 
    { 
    *i = *j++;   // UB? 
    if(*i != ' ') 
     i++; 
    } 
    *i = 0; 
} 

Il ginocchio reazione scatto era che questo codice invoca comportamento indefinito, perché i e j punto nella stessa posizione di memoria, e un'espressione come *i = *j++; sarebbe poi accedere alla stessa variabile due volte, per altri scopi oltre a determinare cosa memorizzare, senza punti di sequenza intermedi. Anche se sono due variabili diverse, inizialmente puntano alla stessa posizione di memoria.

Tuttavia non ne sono sicuro, in quanto non vedo come i due accessi non sequenziati della stessa posizione di memoria possano causare danni alla pratica.

Sono corretto affermando che si tratta di un comportamento non definito? E se sì, ci sono esempi di come fare affidamento su tale UB potrebbe causare comportamenti dannosi?


EDIT

parte rilevante dello standard C che etichettare questo come UB è:

C99 6,5

Tra il precedente e successivo punto sequenza un oggetto deve avere il valore memorizzato modificato al massimo una volta dalla valutazione di un'espressione. Inoltre, il valore precedente deve essere letto solo per determinare il valore da memorizzare.

C11 6,5

Se un effetto collaterale su un oggetto scalare è non in sequenza rispetto a uno un effetto collaterale diverso sullo stesso oggetto scalare o un calcolo valore utilizzando il valore dello stesso oggetto scalare , il comportamento è indefinito. Se sono presenti più ordinamenti consentiti delle sottoespressioni di un'espressione, il comportamento non è definito se si verifica un effetto collaterale di questo tipo, in uno qualsiasi degli ordini.

Il significato effettivo del testo dovrebbe essere lo stesso in entrambe le versioni dello standard, ma credo che il testo C99 sia molto più facile da leggere e capire.

+1

Ti ritrovi 'x = x;' discutibile, troppo? –

+1

@KerrekSB Signore, ma questo caso è simile a 'x = x ++', non è vero? –

+5

@NatashaDutta non lo è. –

risposta

0

Non credo che causerebbe UB. A mio parere, questo è come OK come dicendo

int k=0; 
k=k; //useless but does no harm 

Non farebbe male a leggere i dati dalla memoria e poi scrivere nella stessa posizione.

+0

Ma cosa succede se 'k = k ++'? Che è rilevante per la domanda dell'OP. –

6

Ci sono due situazioni in cui accedono lo stesso oggetto due volte senza un punto intermedio sequenza viene comportamento indefinito:

  1. Se la modifica due volte lo stesso oggetto.Per esempio

    int x = (*p = 1, 1) + (*p = 2, 100); 
    

    Ovviamente non si sa se * p è 1 o 2 dopo questo, ma la formulazione nello standard C dice che si tratta di un comportamento indefinito, anche se si scrive

    int x = (*p = 1, 1) + (*p = 1, 100); 
    

    quindi memorizzare lo stesso valore due volte non ti salva.

  2. Se si modifica l'oggetto, ma lo si legge anche senza utilizzare il valore letto per determinare il nuovo valore dell'oggetto. Ciò significa che

    *p = *p + 1; 
    

va bene, perché si legge *p, si modifica *p, ma avete letto *p al fine di determinare il valore memorizzato in *.

+0

Questa risposta mi piace molto più della mia, anche se la mia risposta non è sbagliata, questa risposta lo spiega molto meglio, suppongo che la mia comprensione del problema non sia al 100% chiara, altrimenti e indipendentemente dal fatto che l'inglese non sia la mia lingua madre, l'avrei espressa meglio. –

+0

Quindi quello che stai dicendo è che il codice _is_ UB, a causa di 2)? Legge 'j' e nella stessa espressione aumenta anche' j' di 1, che non viene fatto allo scopo di determinare quale valore memorizzare in 'i'. – Lundin

0

Suddividere l'espressione *i = *j++. L'ordine di precedenza dei tre operatori è: ++ (incremento post) è il più alto, quindi l'operatore * (puntatore non associato) e = è il più basso.

Quindi, j++ verrà valutato per primo (con un risultato uguale a j e un effetto di incremento j). Quindi l'espressione equivale a

temp = j++; 
*i = *temp; 

dove temp è un compilatore ha generato temporanea che è un puntatore. Nessuna delle due espressioni ha un comportamento indefinito. Il che significa che l'espressione originale non ha nemmeno un comportamento indefinito.

+0

Prima valutato non significa prima eseguito. Penso che più probabilmente il compilatore tradurrà questo in codice macchina come '* i = * j; j ++; 'Non ci dovrebbe essere bisogno di un oggetto temporaneo. A meno che le variabili coinvolte non siano volatili, ma non è questo il caso. – Lundin

+1

Ho incluso il temporaneo perché questa è la semantica di incremento post. Finché produce lo stesso effetto netto, un compilatore può riordinare ed eliminare il temporaneo, come descrivi. Ma non è necessario. – Peter

+0

Sì e per questo motivo non ci sono garanzie che venga creata una variabile temporanea. Se ci fosse una tale garanzia, allora in quel caso il codice non avrebbe sicuramente un comportamento dannoso. – Lundin

3

Non c'è UB qui (è anche idiomatica C), perché:

  • *i viene modificato solo una volta (in *i =)
  • j viene modificato solo una volta (in *j++)

Naturalmente nel codice pubblicato i e j è possibile puntare nello stesso percorso (e fare al primo passaggio) ma ... sono ancora variabili diverse. Quindi, in linea di *i = *j++;:

  • indirizzi vengono letti in entrambi i puntatori (i e j)
  • valore precedente viene letto (* j ++) e viene utilizzato per determinare il valore da memorizzare
  • solo j puntatore viene modificato
  • source è adattato mediante un puntatore non modificato

non è definitivamente UB.


Quelle che seguono invocano UB:

*i = *j++ + *j++; // UB j modified twice 
i = i++ + j;  // UB i modified twice 
+0

Ma per il codice pubblicato, 'source' viene modificato due volte ... Stai dicendo che i nomi delle variabili sono ciò che determina UB, non gli accessi alla memoria? – Lundin

+1

@Lundin: no, l'origine viene modificata solo una volta. L'altro cambia in puntatore j. Ma vedi la mia modifica. - –

+0

Spiacente, non modificato, _accessed_, per altri scopi oltre a determinare quale valore memorizzare. – Lundin

Problemi correlati