Domanda

Ho il seguente codice C:

#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 stampa 191.

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

Io non sono esattamente sicuro perché questo è. Potrei aggirarlo assegnando il valore definito a una variabile in anticipo, ma Preferirei di no farlo.

Qualcuno sa perché questo è? Forse con un link per qualche informazione in più su questo?

È stato utile?

Soluzione

La risposta breve: si compilatore è bacato. (Non c'è nessun problema con troppopieno, come altri hanno suggerito.)

In entrambi i casi, l'aritmetica è fatto in int, che è garantito essere almeno 16 bit. Nel primo frammento è perché 255 è un int, nel secondo è a causa di promozione integrale .

Come annotato, gcc gestisce questo correttamente.

Altri suggerimenti

255 è in corso di elaborazione come un letterale intero e provoca l'intera espressione da int riferiscono piuttosto che sulla base char senza segno. Il secondo caso costringe il tipo di essere corretto. Provare a cambiare la # define come segue:

 #define PRR_SCALE ((uint8_t) 255)

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

Questo è il luogo dove cercare una spiegazione. A causa di ottimizzazioni del compilatore, il codice vero e presentato al processore potrebbe avere non molto somiglianza con il codice originale C (ma di solito fa lo stesso lavoro).

Facendo un passo attraverso le poche istruzioni assembler che rappresenta il codice difettoso dovrebbe rivelare la causa del problema.

La mia ipotesi è che il compilatore ottimizza in qualche modo l'intero calcolo SICE la costante definita è una parte nota al momento della compilazione. 255 * x potrebbe essere ottimizzato per x << 8-x (che è più veloce e più piccolo) Forse qualcosa sta andando male con il codice assembler ottimizzato.

ho preso il tempo per compilare entrambe le versioni sul mio sistema. Con l'ottimizzazione attiva, 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). E qui è il problema. In qualche modo il 255 verrà interpretato come -1 segno a 8 bit invece di 255 senza segno a 16 bit. Oppure viene analizzato a 8 bit prima e poi firmare-esteso a 16 bit. o qualsiasi altra cosa.

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

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 si moltiplica per 3, sei destinato a correre in alcuni problemi di rollover sottili.

Il compilatore potrebbe essere l'ottimizzazione del codice, e pre-calcolare il risultato della vostra espressione matematica e spingendo il risultato in PRR (in quanto si adatta, anche se il valore intermedio non si adatta).

Controlla 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.

Una differenza che posso pensare in caso-1 è,

Il valore letterale PRR_SCALE può andare in ROM o prefisso. E ci può essere qualche differenza nel opecode MUL per dire,

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

Esso non può avere senso a tutti.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top