2009-04-26 13 views
6

Ho il seguente codice C:linguaggio C: il valore #DEFINEd compromette la moltiplicazione a 8 bit. Perché?

#define PRR_SCALE 255 
... 
uint8_t a = 3; 
uint8_t b = 4; 
uint8_t prr; 
prr = (PRR_SCALE * a)/b; 
printf("prr: %u\n", prr); 

Se compilo questo (usando un compilatore piattaforma MSP430, per un piccolo sistema operativo embedded chiamato contiki) il risultato è 0, mentre mi aspettavo 191. (uint8_t è typedef' ed come un unsigned char)

se cambio a:

uint8_t a = 3; 
uint8_t b = 4; 
uint8_t c = 255; 
uint8_t prr; 
prr = (c * a)/b; 
printf("prr: %u\n", prr); 

funziona correttamente e stampe 191.

01.235.

La compilazione di una versione semplice di questo 'normalmente' utilizzando gcc su una casella Ubuntu stampa il valore corretto in entrambi i casi.

Non sono esattamente sicuro del motivo. Potrei aggirarla assegnando in anticipo il valore DEFINEd a una variabile, ma preferirei non farlo.

Qualcuno sa perché questo è? Forse con un link ad altre informazioni su questo?

+0

mi aspetterei che entrambi stampino 191. nel secondo caso, prima c e a vengono promossi a int indipendentemente e quindi la loro moltiplicazione non può eccedere. Lo stesso accade nel primo caso (anche se lì PRR_SCALE è già int - ma non cambierà nemmeno la promozione di a in int). il tuo gcc sulla tua casella si comporta esattamente bene. –

+0

controlla di avere l'intestazione stdio.h inclusa. so che un compilatore per msp430 non consente dichiarazioni di funzioni implicite: se questo è il caso, la chiamata a printf causerebbe un comportamento indefinito, e il risultato "0" sarebbe spiegato in tal modo. solo i miei due centesimi. non credo che valga la pena di rispondere :) –

+0

@litb: stdio.h è incluso. – Rabarberski

risposta

10

La risposta breve: il compilatore è buggato. (Non vi sono problemi con l'overflow, come suggerito da altri.)

In entrambi i casi, l'aritmetica viene eseguita in int, che è garantita per essere lunga almeno 16 bit. Nel primo frammento è perché 255 è un int, nel secondo è a causa di integral promotion.

Come è stato notato, gcc lo gestisce correttamente.

+0

"l'aritmetica viene eseguita in int, che è garantita per almeno 16 bit" - Puoi fornire una fonte su questo? Mi domando è la validità ... – strager

+1

"l'aritmetica è fatta in int": un risultato di promozione integrale. "[int] è garantito per essere almeno lungo 16 bit": garantito indirettamente, INT_MAX deve essere almeno 32767 e INT_MIN al massimo -32767 (vedere la sezione relativa a limits.h nello standard). – avakar

+0

+1, questo è quello che mi aspettavo dopo il debugging un po '. Puoi fornire un link che specifica come riconvertire il valore? O è solo aritmetica modulare? – JaredPar

2

255 viene elaborato come valore letterale intero e fa in modo che l'intera espressione sia basata su int e non su base di caratteri non firmati. Il secondo caso obbliga il tipo a essere corretto. Provare a cambiare la # define come segue:

#define PRR_SCALE ((uint8_t) 255) 
+0

Non funziona, l'avevo provato già (beh, ho fatto il cast direttamente nella linea di calcolo. L'ho ritentato come da lei suggerito. Nessuna differenza). – Rabarberski

+0

Entrambi gli esempi di codice, l'aritmetica è fatta in int.Ciò è dovuto alle promozioni intere, vedi http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf, Sezione 6.3.1.1, paragrafo 2. –

1

Io non sono sicuro perché la definiscono non funziona, ma si potrebbe essere in esecuzione in rollover con le variabili uint8_t. 255 è il valore massimo per uint8_t (2^8 - 1), quindi se lo si moltiplica per 3, si corre inevitabilmente in alcuni sottili problemi di rollover.

Il compilatore potrebbe ottimizzare il codice e precalcolare il risultato dell'espressione matematica e spostare il risultato in prr (poiché si adatta, anche se il valore intermedio non si adatta).

controllare cosa succede se si rompe la vostra espressione come questo (questo non si comporterà come quello che si vuole):

prr = c * a; // rollover! 
prr = prr/b; 

Potrebbe essere necessario utilizzare solo un tipo di dati più grande.

+1

Tecnicamente c * a no troppo pieno. Lo standard C specifica che non può verificarsi un overflow per i tipi di numeri interi senza segno. – JaredPar

+0

Cool, questo ha senso. Immagino che "rollover" sarebbe un termine migliore. –

0

Una differenza che posso pensare in caso-1 è,

Il valore letterale PRR_SCALE può andare in ROM o prefisso. E potrebbe esserci qualche differenza nell'opecode MUL, per esempio,

case-1: [register], [rom] 
case -2: [register], [register] 

Potrebbe non avere alcun senso.

2

Se il compilatore in questione è mspgcc, dovrebbe mettere fuori un elenco assembler del programma compilato insieme al file binario/esadecimale. Altri compilatori potrebbero richiedere flag di compilazione aggiuntivi per farlo. O forse anche un separato disassemblatore funziona sul binario.

Questo è il posto dove cercare una spiegazione. A causa delle ottimizzazioni del compilatore, il codice effettivo presentato al processore potrebbe non avere molta somiglianza con il codice C originale (ma normalmente svolge lo stesso lavoro).

Passare attraverso le poche istruzioni dell'assemblatore che rappresentano il codice difettoso dovrebbe rivelare la causa del problema.

La mia ipotesi è che il compilatore in qualche modo ottimizzi l'intero calcolo, la costante definita è una parte nota in fase di compilazione. 255 * x può essere ottimizzato per x < < 8-x (che è più veloce e più piccolo) Forse qualcosa sta andando storto con il codice assembler ottimizzato.

Mi sono preso il tempo di compilare entrambe le versioni sul mio sistema. Con ottimizzazione attivo, il mspgcc produce il seguente codice:

#define PRR_SCALE 255 
uint8_t a = 3; 
uint8_t b = 4; 
uint8_t prr; 
prr = (PRR_SCALE * a)/b; 
    40ce: 3c 40 fd ff  mov #-3, r12 ;#0xfffd 
    40d2: 2a 42   mov #4, r10 ;r2 As==10 
    40d4: b0 12 fa 6f  call __divmodhi4 ;#0x6ffa 
    40d8: 0f 4c   mov r12, r15 ; 
printf("prr: %u\n", prr); 
    40da: 7f f3   and.b #-1, r15 ;r3 As==11 
    40dc: 0f 12   push r15  ; 
    40de: 30 12 c0 40  push #16576  ;#0x40c0 
    40e2: b0 12 9c 67  call printf  ;#0x679c 
    40e6: 21 52   add #4, r1 ;r2 As==10 

Come si vede, il compilatore calcola direttamente il risultato di 255 * 3 a -3 (0xFFFD). Ed ecco il problema In qualche modo 255 viene interpretato come -1 firmato a 8 bit invece di 255 a 16 bit senza segno. Oppure viene analizzato prima a 8 bit e poi con il segno esteso a 16 bit. o qualsiasi altra cosa

Una discussione su questo argomento è già stata avviata nella mailing list mspgcc.