2016-04-08 29 views
6

Considerate codice semplice:Fa while (i--) s + = a [i]; contiene un comportamento non definito in C e C++?

#include "stdio.h" 

#define N 10U 

int main() { 
    int a[N] = {0}; 
    unsigned int i = N; 
    int s = 0; 

    // Fill a 

    while(i--) 
     s += a[i]; 

    printf("Sum is %d\n", s); 

    return 0; 
} 

Vuol while ciclo contengono undefined comportamento a causa del underflow intero? I compilatori hanno ragione di ritenere che la condizione del ciclo while sia sempre vera a causa di ciò e finisca con un ciclo infinito?

Cosa succede se i è signed int? Non contiene insidie ​​legate all'accesso agli array?

Aggiornamento

corro questo e codice simile più volte e ha funzionato bene. Inoltre, è un modo popolare per scorrere oltre array e vettori indietro. Sto facendo questa domanda per assicurarmi che questa via sia OK dal punto di vista dello standard.

A colpo d'occhio, ovviamente non è infinito. D'altra parte, a volte il compilatore può "ottimizzare" alcune condizioni e il codice presupponendo che il codice non contenga alcun comportamento indefinito. Può portare a loop infiniti e altre conseguenze indesiderate. Vedi this.

+3

Bene, questo è ** non ** un ciclo infinito. –

+2

Cosa è successo quando lo hai eseguito? –

+0

Nessun problema, dal momento che 'while (0)' è falso e non ti interessa il valore di 'i' dopo – MrTux

risposta

6

Il ciclo contiene un comportamento non definito a causa dell'intero underflow?

No, overflow/underflow è solo un comportamento non definito in caso di interi con segno.

I compilatori hanno ragione di presumere che la condizione del ciclo è sempre vera a causa di ciò e si conclude con un ciclo infinito?

No, perché l'espressione alla fine si rivelerà pari a zero.

Cosa succede se ho firmato int? Non contiene insidie ​​legate all'accesso agli array?

Se è firmato e over/underflows, si richiama un comportamento non definito.

+0

's/è solo un comportamento non definito/è possibile solo /' –

+0

@LightnessRacesinOrbit Sentiti libero di inviare una correzione anche al comitato standard C, 3.4.3 '" ESEMPIO Un esempio di comportamento non definito è il comportamento sull'overflow di interi. " ' – Lundin

+1

Questa affermazione è corretta. Ma i valori non firmati non eccedono, quindi non è pertinente a questa situazione. Anche la frase nella tua risposta è tecnicamente corretta, ma il mio punto è che penso che suggerisca la cosa sbagliata: l'overflow/underflow non è un comportamento _defined_ per interi_identificati perché è impossibile! :) Come tale, il mio suggerimento era di natura redazionale. –

9

Questo codice non richiama il comportamento non definito. Il ciclo verrà interrotto una volta i diventa 0.

Per unsigned int, non esiste alcun numero intero/inferiore. L'effetto sarà lo stesso con i come signed tranne che in questo caso non verrà eseguito il wrapping.

+0

Perché giù voto? – haccks

+2

Questo è codificato in C++, c'è un rapporto in basso di 1/5 in effetto per tutte le risposte. (Avere un +1 per compensare. –

3

i sarà avvolgere intorno al ~0 (0XFFFFFFFF per 32 bit) ma il loop terminerà quindi non c'è UB.

+0

"ma il ciclo terminerà quindi non ci sarà nessun UB." - nononono, è il contrario. ** Perché ** non c'è UB, ** quindi ** il compilatore deve emettere il codice che fa terminare il ciclo. La ragione per cui viene definito il comportamento, a sua volta, è che l'oggetto decrementato ('i') è' unsigned'. –

4

Il ciclo non produce un comportamento non definito per i seguenti motivi.

i viene inizializzato su 10 e decrementato nel ciclo. Quando i ha un valore pari a zero, decrementando produce un valore pari a UINT_MAX - il valore più grande che può rappresentare un unsigned, ma il ciclo terminerà.a[i] sarà sempre accessibile (all'interno del loop) per i valori di i tra N-1 (ad esempio 9) e 0. Questi sono tutti indici validi nell'array a.

s e tutti gli elementi di a vengono inizializzati a zero. Quindi tutte le aggiunte aggiungono 0 a 0. Ciò non eccederà mai o non traboccherà un int, quindi non potrà mai comportare un comportamento indefinito.

Se i viene modificato in signed int, il decremento non underflow, e i avrà un valore negativo quando il ciclo termina. L'unica variazione netta è nel valore che i ha dopo il ciclo.

0

Il ciclo contiene un comportamento non definito a causa dell'intero underflow?

Innanzitutto questa è una Boolean Conversion:

Un prvalue di integrale, virgola mobile, l'enumerazione senza ambito, puntatore, e tipi puntatore-a-membro può essere convertito in un prvalue di tipo bool.

Il valore zero (per l'enumerazione integrale, a virgola mobile e senza ambito) e il puntatore nullo ei valori da puntatore a membro nullo diventano false. Tutti gli altri valori diventano true.

Quindi non ci sarà alcun integer underflow se i sia correttamente inizializzato, raggiungerà 0U e che sarà lanciato ad un valore false.

Così che cosa il compilatore sta facendo in modo efficace ecco: while(static_cast<bool>(i--))

Non compilatori hanno diritto di presumere che, mentre condizione del ciclo è sempre true a causa di questo e finire con ciclo infinito?

Il motivo chiave per cui questo non è un ciclo infinito è che il valore postfix decrement operator restituisce il valore della variabile decrementata. È definito come T operator--(T&, int) E come discusso in precedenza, il compilatore sta valutando se tale valore è 0U come parte della conversione in bool.

Quello che il compilatore è effettivamente facendo qui è: while(0 != operator--(i, 1))

Nel vostro aggiornamento di citare questo codice come una motivazione per la tua domanda: comportamento

void fn(void) 
{ 
    /* write something after this comment so that the program output is 10 */ 
    int a[1] = {0}; 
    int j = 0; 
    while(a[j] != 5) ++j; /* Search stack until you find 5 */ 
    a[j] = 10;    /* Overwrite it with 10 */ 
    /* write something before this comment */ 
} 

A un esame, che l'intero programma è undefined c'è solo 1 elemento di a ed è inizializzato a 0. Pertanto, per qualsiasi indice diverso da 0, a[j] sta guardando alla fine dell'array. Continuerà fino a quando non viene trovato un 5 o fino a quando il sistema operativo non funziona perché il programma ha letto dalla memoria protetta. Questo è diverso dalla condizione del loop che uscirà quando l'operatore di decremento postfix restituirà un valore pari a 0, quindi non è possibile supporre che questo sia sempre true o che il ciclo continui all'infinito.

Cosa succede se i è signed int?

Entrambe le conversioni di cui sopra sono incorporate in C++. E sono entrambi definiti per tutti i tipi interi, firmati e non firmati. Quindi in definitiva questo si espande a:

while(static_cast<bool>(operator--(i, 1))) 

Non contiene insidie ​​relative all'accesso agli array?

Anche in questo caso sta chiamando un operatore di built-in, la subscript operator: T& operator[](T*, std::ptrdiff_t)

Così a[i] è l'equivalente di chiamare operator(a, static_cast<ptrdiff_t>(i))

Quindi la domanda ovvia follow è ciò che è un ptrdiff_t? È un intero definito dall'implementazione, ma in quanto tale ogni implementazione dello standard è responsabile della definizione delle conversioni da e verso questo tipo, pertanto i verrà eseguito correttamente.

+0

Cosa c'entra l'operatore "restituire un riferimento"? E come entra in gioco 'operator ++'? Distinti, confusi. –

+0

@LightnessRacesinOrbit Grazie. Digitato la cosa sbagliata su 'operator ++'. Ma cosa intendi, "Cosa c'entra l'operatore - 'restituire un riferimento a qualcosa?" Se ciò non restituisce qualcosa, la condizione 'while' 'non ha nulla da valutare? –

+1

Potrebbe restituire di valore invece ... e, in effetti, lo fa. Il decremento di Postfix _has to_ (e il decremento del prefisso per i tipi di aritmetica incorporati fa comunque, per riferimento a cui è stato collegato). Inoltre, è ovvio che 'i -' valuti qualcosa altrimenti il ​​codice non verrebbe compilato. Penso che l'intera sezione sia una falsa pista per questa domanda. –

1

Il codice è ben comportata e non contiene comportamento non definito per ogni valore di N ≥ 0.

concentriamoci sul ciclo while, e traccia attraverso un esempio di esecuzione con N = 2.

// Initialization 
#define N 2U 
unsigned int i = N; // i = 2 

// First iteration 
while(i--) // condition = i = 2 = true; i post-decremented to 1 
    s += a[i]; // i = 1 is in bounds 

// Second iteration 
while(i--) // condition = i = 1 = true; i post-decremented to 0 
    s += a[i]; // i = 0 is in bounds 

// Third iteration 
while(i--) // condition = i = 0 = false; i post-decremented to 0xFFFFFFFF 
// Loop terminated 

possiamo vedere da questa traccia che a[i] è sempre in limiti, e i sperimenta un'avvolgente non firmato quando decremento da 0, che è perfettamente definita.

Per rispondere alla tua seconda domanda, se abbiamo cambiato il tipo di i-signed int, l'unico comportamento che cambia nell'esempio traccia è che il ciclo termina nello stesso luogo, ma i viene decrementato a -1, che è anche perfettamente -defined.

Quindi, in conclusione, il codice è ben educato presupponendo che N ≥ 0, sia i sia unsigned int o signed int. (Se N è negativo e è signed int, il ciclo continuerà a decrescere fino a quando non si verifica un comportamento non definito a INT_MIN.)

+0

Spiegazione molto chiara, grazie! –