Question

J'ai le code C suivant:

#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);

Si je compile ce (en utilisant un compilateur plate-forme MSP430, pour un petit OS embarqué appelé contiki ) le résultat est 0 alors que je m'y attendais 191. (Uint8_t est typedef comme un unsigned char)

Si je change à:

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

il fonctionne correctement et imprime 191.

Compiler une version simple de cette « normalement » en utilisant gcc sur une boîte Ubuntu imprime la valeur correcte dans les deux cas.

Je ne sais pas exactement pourquoi. Je pourrais le contourner en affectant la valeur définie à une variable à l'avance, mais je préfère ne pas le faire.

Quelqu'un sait-il pourquoi est-ce? Peut-être avec un lien vers un peu plus d'informations à ce sujet?

Était-ce utile?

La solution

La réponse courte: vous compilateur est bogué. (Il n'y a pas de problème avec trop-plein, comme d'autres ont suggéré.)

Dans les deux cas, le calcul est fait en int, qui est garanti pour être d'au moins 16 bits de long. Dans le premier extrait, il est parce que 255 est un int, dans ce dernier, il est à cause de promotion intégrale .

Comme vous l'avez noté, gcc gère cela correctement.

Autres conseils

255 est traité comme un nombre entier littéral et provoque l'expression entière soit int plutôt que de l'unsigned char. Le deuxième cas force le type à être correct. Essayez de changer #define comme suit:

 #define PRR_SCALE ((uint8_t) 255)

Si le compilateur en question est le mspgcc, il devrait mettre sur une liste assembleur du programme compilé avec le fichier binaire / hexadécimal. D'autres compilateurs peuvent exiger des drapeaux de compilateur supplémentaires pour le faire. Ou peut-être même un désassembleur séparé exécuté sur le binaire.

Ceci est l'endroit où chercher une explication. En raison des optimisations du compilateur, le code réel présenté au processeur pourrait avoir pas beaucoup similitude avec le code d'origine C (mais fait normalement le même travail).

Parcourant les quelques instructions assembleur représentant le code défectueux devrait révéler la cause du problème.

Je suppose que le compilateur optimise en quelque sorte tout le calcul SICE l'est une partie connue constante définie au moment de la compilation. 255 * x pourrait être optimisé pour x << 8-x (qui est plus rapide et plus petit) Peut-être que quelque chose va mal avec le code assembleur optimisé.

Je pris le temps de compiler les deux versions sur mon système. Avec l'optimisation active, le mspgcc produit le code suivant:

#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

Comme on peut le voir, le compilateur calcule directement le résultat de 255 * 3 pour 3 (0xfffd). Et est le problème ici. D'une certaine façon le 255 est interprété comme -1 signé 8 bits au lieu de 255 bits non signé 16. Ou il est analysé à 8 bits d'abord, puis signe étendu à 16 bits. ou autre.

Une discussion sur ce sujet a été lancé à la liste de diffusion de mspgcc déjà.

Je ne sais pas pourquoi le définir ne fonctionne pas, mais vous pourriez être en cours d'exécution dans les renversements avec les variables uint8_t. 255 est la valeur maximale pour uint8_t (2^8 - 1), donc si vous multipliez ce chiffre par 3, vous êtes lié à rencontrer quelques problèmes de renversement subtils.

Le compilateur peut être l'optimisation du code et pré-calcul du résultat de votre expression mathématique et bousculant le résultat dans le PRR (puisqu'il convient, même si la valeur intermédiaire ne correspond pas).

Vérifiez ce qui se passe si vous cassez votre expression comme celui-ci (ce qui ne se comportera pas comme ce que vous voulez):

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

Vous devrez peut-être il suffit d'utiliser un type plus grand.

Une différence que je peux penser au cas-1 est,

La valeur littérale PRR_SCALE peut aller dans la ROM ou d'une zone de code. Et il peut y avoir une certaine différence dans la opecode MUL pour dire,

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

Il ne peut pas faire sens.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top