2009-06-12 13 views
20

Stavo leggendo lo standard C l'altro giorno e ho notato che, a differenza dell'overflow del numero intero con segno (che non è definito), l'overflow dei numeri interi senza segno è ben definito. L'ho visto usato in un sacco di codice per i massimi, ecc. Ma dato il voodoos sull'overflow, è considerata una buona pratica di programmazione? È comunque insicuro? So che molti linguaggi moderni come Python non lo supportano, invece continuano a estendere la dimensione di grandi numeri.L'uso di overflow di interi senza segno è una buona pratica?

risposta

20

L'overflow di interi senza segno (nella forma di wrap-around) viene regolarmente sfruttato nelle funzioni di hashing, ed è stato dal punto dell'anno.

+1

punto anno: è il 1970? –

+0

Poiché le origini dell'informatica sono in crittografia, sospetto che sia stato negli anni '40. –

+0

Anche se in realtà non capisco perché insistere sull'overflow * signed * integer. L'utilizzo di un numero intero senza segno è altrettanto valido e può essere risolto per rilevare l'overflow. –

3

Un modo in cui potrei pensare a un overflow di numeri interi senza segno che causa un problema si ha quando si sottrae da un piccolo valore senza segno, risultante in un valore positivo di grandi dimensioni.

Consigli pratici per integer overflow:
http://www.gnu.org/software/hello/manual/autoconf/Integer-Overflow-Basics.html#Integer-Overflow-Basics

+1

Mi chiedo informazioni sull'overflow senza segno – user122147

+0

Questo è senza segno. Usando l'aritmetica non firmata, sottrarre 200 da 100. Otterrai un valore elevato (quanto grande dipende dall'intervallo di valori). L'aritmetica senza segno in C è definita come modulare, o si potrebbe dire avvolgere. –

+0

@DavidThornley: Penso che la questione fosse se è una buona pratica affidarsi a un simile comportamento. Il mio pensiero personale è che è bene fare affidamento su un comportamento del genere se e solo se si utilizzano le tipografie rigide ogni volta che si desidera applicarlo. Se 'a' e' b' sono ad es.'UInt32', l'espressione' (UInt32) (a-b) 'produrrà un comportamento di avvolgimento, e sarà chiaro che tale comportamento è previsto. L'aspettativa che (a-b) dovrebbe dare un comportamento di wrapping, tuttavia, è molto meno ovvia, e su macchine dove 'int' è più grande di 32 bit, potrebbe infatti * non * produrre un simile comportamento. – supercat

1

non vorrei contare su di esso solo per ragioni di leggibilità. Stai eseguendo il debug del codice per ore prima di capire dove stai reimpostando la variabile su 0.

+0

Non proprio, a condizione che il comportamento sia documentato e commentato. Inoltre, vi sono motivi legittimi per l'overflow dei numeri interi non firmati, ad esempio in timestamp a larghezza fissa, in cui la precisione è più importante e l'overflow può essere ragionevolmente rilevato. –

0

Poiché i numeri firmati sulle CPU possono essere rappresentati in modi diversi, il 99,999% di tutte le CPU attuali utilizza la notazione a due complementi. Poiché questa è la maggior parte delle macchine là fuori, è difficile trovare un comportamento diverso anche se il compilatore potrebbe controllarlo (probabilità di grasso). Le specifiche C tuttavia devono rappresentare il 100% dei compilatori quindi non hanno definito il suo comportamento.

Quindi renderebbe le cose più confuse, che è una buona ragione per evitarlo. Tuttavia, se hai una buona ragione (ad esempio, incremento delle prestazioni del fattore 3 per la parte critica del codice), documentala bene e usala.

+2

Si sta facendo riferimento a interi con segno. Lo standard C documenta il comportamento degli interi senza segno, ed è di questo che si tratta. –

2

Lo uso sempre per sapere se è ora di fare qualcosa.

UInt32 now = GetCurrentTime() 

if(now - then > 100) 
{ 
    // do something 
} 

Finché si controlla il valore prima di 'società' giri 'poi', si sta bene per tutti i valori di 'società' e 'quindi'.

EDIT: Immagino che questo sia davvero un underflow.

+0

Credo che il codice sopra non funzionerà nei sistemi in cui un 'int' è 64 bit, poiché gli operandi della sottrazione verrebbero estesi agli interi con segno a 64 bit, quindi se 'now' è 0 e' then' è 4294967295u (0xFFFFFFFF) il risultato della sottrazione non sarebbe 1 ma -4294967295. Trasmettere il risultato della sottrazione a un UInt32 prima che il confronto evitasse quel problema poiché (UInt32) (- 4294967295) sarebbe 1. – supercat

1

Un altro luogo dove troppo pieno non firmato può essere utilizzato utilmente è quando si deve scorrere all'indietro da un determinato tipo unsigned:

void DownFrom(unsigned n) 
{ 
    unsigned m; 

    for(m = n; m != (unsigned)-1; --m) 
    { 
     DoSomething(m); 
    } 
} 

Altre alternative non sono così pulito. Provare a fare m >= 0 non funziona a meno che non si cambi m in firmato, ma in questo caso si potrebbe troncare il valore di n - o peggio - convertendolo in un numero negativo all'inizializzazione.

Altrimenti devi fare! = 0 o> 0 e quindi eseguire manualmente il caso 0 dopo il ciclo.

+1

per (m = n; m

+2

Oppure "do {DoSomething (n);} while (n--! = 0); ". Con "if (n! = (Unsigned) -1)" all'inizio, se è effettivamente desiderabile che -1 sia un valore di input magico che significa "non fare nulla", come nel codice precedente. –

+0

@Niki: non funziona, il tuo ciclo non fa nulla dato che m

3

Per dirla in breve:

è perfettamente legale/OK/sicuro da usare integer overflow senza segno, come si vede in forma fino a quando si presta attenzione e rispettare la definizione (per qualsiasi scopo - ottimizzazione, super algoritmi intelligenti, ecc.)

0

Va bene fare affidamento sull'overflow finché si sa QUANDO si verificherà ...

I, ad esempio, ha avuto problemi con l'implementazione C di MD5 durante la migrazione a un compilatore più recente ... Il codice si aspettava un overflow ma prevedeva anche 32 bit di input.

Con 64 bit i risultati erano sbagliati!

Fortunatamente questo è quello che i test automatici sono per: ho preso il problema in anticipo ma questa potrebbe essere stata una vera storia dell'orrore se passata inosservata.

Si potrebbe obiettare "ma questo succede raramente": sì ma questo è ciò che lo rende ancora più pericoloso! Quando c'è un bug, tutti sono sospettosi del codice scritto negli ultimi giorni. Nessuno è il codice f sospetto che "solo lavorato per anni" e di solito non si sa ancora come funziona ...

+0

Questo è un caso in cui un'asserzione in fase di compilazione per verificare l'ipotesi che sizeof (int) == 4 fosse valida sarebbe una buona cosa. Ciò avrebbe generato un errore anche prima dell'esecuzione dei test. – RBerteig

3

Solo perché si conosce il minuzie dello standard non significa che la persona che mantiene il codice fa. Questa persona potrebbe dover sprecare tempo a preoccuparsi di ciò mentre eseguirà il debug in un secondo momento, o dovrà andare a cercare lo standard per verificare questo comportamento in seguito.

Certo, ci aspettiamo competenza con le caratteristiche ragionevoli di una lingua in un programmatore che lavora - e diverse società/gruppi hanno aspettative diverse su dove sia quella ragionevole competenza. Ma per la maggior parte dei gruppi questo sembra essere un po 'troppo aspettarsi che la prossima persona sappia in cima alla sua testa e non debba pensarci.

Se ciò non bastasse, è più probabile che si verifichino errori del compilatore quando si lavora intorno ai bordi dello standard. O peggio, la persona che porta questo codice su una nuova piattaforma può imbattersi in loro.

In breve, voto non farlo!

0

Se lo si utilizza saggiamente (ben commentato e leggibile), è possibile usufruire di esso con un codice più piccolo e più veloce.

0

siukurnin fa un buon punto, che è necessario sapere quando si verificheranno straripamenti. Il modo più semplice per evitare il problema della portabilità descritto è quello di utilizzare i tipi interi a larghezza fissa da stdint.h. uint32_t è un numero intero a 32 bit senza segno su tutte le piattaforme e tutti i sistemi operativi e non si comporta in modo diverso quando viene compilato per un sistema diverso.

0

Suggerirei sempre di avere un cast esplicito ogni volta che si farà affidamento su numeri non firmati. Altrimenti potrebbero esserci sorprese. Ad esempio, se "Int" è di 64 bit, il codice come:

UInt32 foo,bar; 
if ((foo-bar) < 100) // Report if foo is between bar and bar+99, inclusive) 
    ... do something 

potrebbe non riuscire, dal momento che "foo" e "bar" otterrebbe promosso a interi con segno a 64 bit. Aggiungendo un typecast a UInt32 prima di verificare il risultato con 100 si eviterebbero problemi in quel caso.

Incidentalmente, credo che l'unico modo portatile per ottenere direttamente i 32 bit inferiori del prodotto di due UInt32 sia quello di eseguire il cast di uno degli input su un UInt64 prima di moltiplicare. In caso contrario, gli UInt32 potrebbero essere convertiti in Int64 con segno, con la moltiplicazione che trabocca e produce risultati indefiniti.

Problemi correlati