Se un numero è troppo grande, si riversa nella posizione di memoria successiva?

30

Ho esaminato la programmazione in C e ci sono solo un paio di cose che mi infastidiscono.

Prendiamo questo codice per esempio:

int myArray[5] = {1, 2, 2147483648, 4, 5};
int* ptr = myArray;
int i;
for(i=0; i<5; i++, ptr++)
    printf("\n Element %d holds %d at address %p", i, myArray[i], ptr);

So che un int può contenere un valore massimo di 2.147.483.647 positivi. Quindi andando su uno di questi, si "riversa" sull'indirizzo di memoria successivo che fa apparire l'elemento 2 come "-2147483648" a quell'indirizzo? Ma questo non ha molto senso perché nell'output dice ancora che l'indirizzo successivo contiene il valore 4, quindi 5. Se il numero si è rovesciato sull'indirizzo successivo, non cambierebbe il valore memorizzato in quell'indirizzo ?

Ricordo vagamente di aver programmato in Assembly MIPS e guardando gli indirizzi cambiare i valori durante il programma passo dopo passo che i valori assegnati a quegli indirizzi sarebbero cambiati.

A meno che non mi ricordi male, ecco un'altra domanda: se il numero assegnato a un indirizzo specifico è più grande del tipo (come in myArray [2]), allora non influisce sui valori memorizzati all'indirizzo successivo?

Esempio: abbiamo int myNum = 4 miliardi all'indirizzo 0x10010000. Ovviamente myNum non può memorizzare 4 miliardi quindi appare come un numero negativo a quell'indirizzo. Nonostante non sia in grado di memorizzare questo numero elevato, non ha alcun effetto sul valore memorizzato all'indirizzo successivo di 0x10010004. Corretto?

Gli indirizzi di memoria hanno spazio sufficiente per contenere determinate dimensioni di numeri / caratteri, e se la dimensione supera il limite, verrà rappresentata in modo diverso (come provare a memorizzare 4 miliardi nell'int ma apparirà come un negativo numero) e quindi non ha alcun effetto sui numeri / caratteri memorizzati al prossimo indirizzo.

Scusa se sono andato fuori bordo. Ho avuto una grande scoreggia cerebrale tutto il giorno da questo.

    
posta stumpy 20.01.2016 - 08:33
fonte

5 risposte

48

No, non è così. In C, le variabili hanno un set fisso di indirizzi di memoria con cui lavorare. Se stai lavorando su un sistema con 4 byte ints e imposti una variabile int a 2,147,483,647 e quindi aggiungi 1 , la variabile conterrà solitamente -2147483648 . (Sulla maggior parte dei sistemi, il comportamento è in realtà non definito.) Nessun'altra posizione di memoria verrà modificata.

In sostanza, il compilatore non ti consente di assegnare un valore troppo grande per il tipo. Questo genererà un errore del compilatore. Se lo imponi con un caso, il valore verrà troncato.

Considerato a bit per bit, se il tipo può solo memorizzare 8 bit e si tenta di forzare il valore 1010101010101 in esso con un caso, si finirà con gli 8 bit in basso o con 01010101 .

Nel tuo esempio, indipendentemente da ciò che fai a myArray[2] , myArray[3] conterrà '4'. Non c'è "spill over". Stai provando a mettere qualcosa che è più di 4-byte, ma si limiterà a tutto ciò che si trova nella fascia alta, lasciando i 4 byte in basso. Sulla maggior parte dei sistemi, questo risulterà in -2147483648 .

Da un punto di vista pratico, vuoi solo assicurarti che non succeda mai, mai. Questi tipi di overflow spesso provocano difetti difficili da risolvere. In altre parole, se ritieni che ci siano dei rischi, i tuoi valori saranno espressi in miliardi, non utilizzare int .

    
risposta data 20.01.2016 - 09:00
fonte
24

L'overflow del numero intero con segno è un comportamento non definito. Se ciò accade, il tuo programma non è valido. Il compilatore non è tenuto a controllarlo, quindi potrebbe generare un eseguibile che sembra fare qualcosa di ragionevole, ma non è garantito che lo farà.

Tuttavia, l'overflow dei numeri interi senza segno è ben definito. Avvolgerà il modulo UINT_MAX + 1. La memoria non occupata dalla tua variabile non sarà influenzata.

Vedi anche link

    
risposta data 20.01.2016 - 14:13
fonte
14

Quindi, ci sono due cose qui:

  • il livello linguistico: quali sono le semantiche di C
  • il livello della macchina: quali sono le semantiche dell'assieme / CPU che usi

A livello di lingua:

In C:

  • overflow e underflow sono definiti come modulo aritmetico per interi non firmati , quindi il loro valore "loops"
  • overflow e underflow sono Comportamento indefinito per interi firmati , quindi può succedere di tutto

Per quelli che vorrebbero un esempio "what anything", ho visto:

for (int i = 0; i >= 0; i++) {
    ...
}

trasformarsi in:

for (int i = 0; true; i++) {
    ...
}

e sì, questa è una trasformazione legittima.

Significa che ci sono effettivamente rischi potenziali di sovrascrivere la memoria in overflow a causa di alcune strane trasformazioni del compilatore.

Nota: su Clang o gcc usa -fsanitize=undefined in Debug per attivare Disinfezione del comportamento non definito che interromperà in underflow / overflow di interi con segno.

O significa che è possibile sovrascrivere la memoria utilizzando il risultato dell'operazione per indicizzare (deselezionato) in una matrice. Questo è purtroppo molto più probabile in assenza di rilevamento di underflow / overflow.

Nota: su Clang o gcc usa -fsanitize=address in Debug per attivare Address Sanitizer che interromperà l'accesso fuori dai limiti.

A livello di macchina :

Dipende molto dalle istruzioni di assemblaggio e dalla CPU che usi:

  • su x86, ADD utilizzerà 2-complementi su overflow / underflow e imposterà OF (Overflow Flag )
  • sulla futura CPU Mill, ci saranno 4 diverse modalità di overflow per Add :
    • Modulo: modulo a 2 complementi
    • Trap: viene generato un trap che blocca il calcolo
    • Saturate: il valore si blocca su min in underflow o max on overflow
    • Double Width: il risultato viene generato in un registro a larghezza doppia

Si noti che le cose accadono nei registri o nella memoria, in entrambi i casi la CPU sovrascrive la memoria in caso di overflow.

    
risposta data 20.01.2016 - 16:57
fonte
4

Per ulteriore risposta di @StevenBurnap, la ragione per cui questo accade dipende da come i computer lavorano a livello macchina.

Il tuo array è memorizzato in memoria (ad esempio nella RAM). Quando viene eseguita un'operazione aritmetica, il valore in memoria viene copiato nei registri di input del circuito che esegue l'aritmetica (la ALU: Aritmetica logica Unità ), l'operazione viene quindi eseguita sui dati nei registri di ingresso, producendo un risultato nel registro di uscita. Questo risultato viene quindi copiato nella memoria all'indirizzo corretto in memoria, lasciando intatte altre aree di memoria.

    
risposta data 20.01.2016 - 15:49
fonte
4

Primo (supponendo lo standard C99), potresti voler includere <stdint.h> intestazione standard e usare alcuni dei tipi definiti lì, in particolare int32_t che è esattamente un intero con segno a 32 bit o uint64_t che è esattamente un intero senza segno a 64 bit e così via. Potresti voler utilizzare tipi come int_fast16_t per motivi di prestazioni.

Leggi altre risposte spiegando che l'aritmetica non firmata non versa mai (o trabocca) in posizioni di memoria adiacenti. Attenti al comportamento indefinito sull'overflow firmato .

Quindi, se hai bisogno di calcolare esattamente numeri interi enormi (es. vuoi calcolare fattoriale di 1000 con tutte le sue 2568 cifre in decimale), vuoi bigints aka numeri precisi di arbitraggio (o bignums). Gli algoritmi per l'aritmetica dei bigint efficienti sono molto intelligenti e, di solito, richiedono l'uso di istruzioni macchina specializzate (ad esempio alcuni aggiungono word con carry, se il processore lo ha). Quindi in questo caso consiglio vivamente di utilizzare alcune librerie bigint esistenti come GMPlib

    
risposta data 20.01.2016 - 17:28
fonte

Leggi altre domande sui tag