C Entier Promotion sur les MCU 8 bits

14

En utilisant avr-gcc comme exemple, les types int sont spécifiés pour avoir une largeur de 16 bits. L'exécution d'opérations sur des opérandes 8 bits en C entraîne la conversion de ces opérandes en types int 16 bits en raison de la promotion des nombres entiers en C. Cela signifie-t-il que toutes les opérations arithmétiques 8 bits sur un AVR prendront beaucoup plus de temps si elles sont écrites en C que si écrit en assemblage en raison de la promotion de l'entier C?

pr871
la source
1
Je ne pense pas, le compilateur se rendra compte que la variable de destination est un caractère (non signé), donc cela ne dérangera pas dans le calcul des 8 premiers bits. Pourtant, j'ai trouvé que GCC n'est parfois pas si bon pour optimiser le code, donc si vous codez en ASM, le résultat MGIHT sera plus rapide. Cependant, à moins que vous ne fassiez des tâches / interruptions très urgentes, avec une contrainte budgétaire très forte, alors vous devriez choisir un processeur plus puissant et le programmer en C, ou tout simplement ne vous inquiétez pas des performances inférieures (pensez plutôt au temps -à-marché, meilleure lisibilité / réutilisabilité du code, moins de bugs, etc.).
next-hack
Je m'excuse de ne pas avoir eu le temps de vérifier. Cependant, je pense qu'il y avait un drapeau de ligne de commande pour gcc qui contrôlerait la «promotion entière». Il peut même y avoir un pragma pour le contrôler pour des morceaux de code spécifiques. Quelle est l'importance des performances? Dans de nombreuses utilisations d'un AVR, la différence de vitesse pour certaines opérations arithmétiques n'est pas un problème. Foxus sur le premier fonctionnement correct du code. Ensuite, s'il y a un problème de performances, découvrez ce que c'est. Il serait facile de perdre du temps à coder dans l'assembleur, seulement vous trouverez que cela n'a pas d'importance.
gbulmer
1
il suffit de démonter et de voir ce que fait le compilateur. D'un point de vue purement linguistique, oui. l'implémentation ici est atypique. normalement, int essaie de s'aligner sur la taille du registre, et si vous aviez des registres 16 bits, les calculs 8 bits sont en fait moins chers à 16 bits que 8. Mais c'est l'inverse et avec un mcu 8 bits, il est logique d'implémenter int comme 16 bits. vous devriez donc probablement utiliser uchar là où vous vous souciez de cela, mais n'en faites pas une habitude de programmation courante car cela vous fait le plus mal partout ailleurs.
old_timer
3
N'oubliez pas: évitez de répondre aux questions dans les commentaires.
pipe
4
Ce type de questions est préférable de poser aux experts C de SO, car il s'agit d'une pure question logicielle. La promotion entière en C est un sujet quelque peu complexe - le programmeur C moyen aura beaucoup d'idées fausses à ce sujet.
Lundin

Réponses:

16

Longue histoire courte:

La promotion d'entier en 16 bits a toujours lieu - la norme C l'impose. Mais le compilateur est autorisé à optimiser le calcul jusqu'à 8 bits (les compilateurs de systèmes embarqués sont généralement assez bons pour de telles optimisations), s'il peut déduire que le signe sera le même qu'il aurait été si le type avait été promu.

Ce n'est pas toujours le cas! Les modifications de signature implicites causées par la promotion d'entiers sont une source courante de bogues dans les systèmes embarqués.

Une explication détaillée peut être trouvée ici: Règles de promotion de type implicite .

Lundin
la source
8
unsigned int fun1 ( unsigned int a, unsigned int b )
{
    return(a+b);
}

unsigned char fun2 ( unsigned int a, unsigned int b )
{
    return(a+b);
}

unsigned int fun3 ( unsigned char a, unsigned char b )
{
    return(a+b);
}

unsigned char fun4 ( unsigned char a, unsigned char b )
{
    return(a+b);
}

comme fun1 attendu est tous les pouces, tout comme les mathématiques 16 bits

00000000 <fun1>:
   0:   86 0f           add r24, r22
   2:   97 1f           adc r25, r23
   4:   08 95           ret

Bien que techniquement incorrect car il s'agit d'un ajout 16 bits appelé par le code, même non optimisé, ce compilateur a supprimé l'adc en raison de la taille du résultat.

00000006 <fun2>:
   6:   86 0f           add r24, r22
   8:   08 95           ret

pas vraiment surpris ici que la promotion se produise, les compilateurs ne le faisaient pas, je ne sais pas quelle version a fait que ce début se produise, je l'ai rencontré tôt dans ma carrière et malgré les compilateurs faisant la promotion hors service (comme ci-dessus), faisant la promotion même si je lui a dit de faire des mathématiques uchar, pas surpris.

0000000a <fun3>:
   a:   70 e0           ldi r23, 0x00   ; 0
   c:   26 2f           mov r18, r22
   e:   37 2f           mov r19, r23
  10:   28 0f           add r18, r24
  12:   31 1d           adc r19, r1
  14:   82 2f           mov r24, r18
  16:   93 2f           mov r25, r19
  18:   08 95           ret

et l'idéal, je sais que c'est 8 bits, je veux un résultat 8 bits alors je lui ai simplement dit de faire 8 bits tout au long.

0000001a <fun4>:
  1a:   86 0f           add r24, r22
  1c:   08 95           ret

Donc, en général, il vaut mieux viser la taille du registre, qui est idéalement la taille d'un (u) int, pour un mcu 8 bits comme celui-ci, les auteurs du compilateur ont dû faire un compromis ... Le point étant de ne pas prendre l'habitude de utiliser uchar pour les mathématiques que vous savez n'a pas besoin de plus de 8 bits comme lorsque vous déplacez ce code ou écrivez un nouveau code comme celui-ci sur un processeur avec des registres plus grands, le compilateur doit maintenant commencer à masquer et à étendre les signes, ce que certains font nativement dans certaines instructions, et d'autres non.

00000000 <fun1>:
   0:   e0800001    add r0, r0, r1
   4:   e12fff1e    bx  lr

00000008 <fun2>:
   8:   e0800001    add r0, r0, r1
   c:   e20000ff    and r0, r0, #255    ; 0xff
  10:   e12fff1e    bx  lr

forcer 8 bits coûte plus cher. J'ai triché un peu / beaucoup, j'aurais besoin d'exemples un peu plus compliqués pour en voir plus de manière juste.

EDIT basé sur la discussion des commentaires

unsigned int fun ( unsigned char a, unsigned char b )
{
    unsigned int c;
    c = (a<<8)|b;
    return(c);
}

00000000 <fun>:
   0:   70 e0           ldi r23, 0x00   ; 0
   2:   26 2f           mov r18, r22
   4:   37 2f           mov r19, r23
   6:   38 2b           or  r19, r24
   8:   82 2f           mov r24, r18
   a:   93 2f           mov r25, r19
   c:   08 95           ret

00000000 <fun>:
   0:   e1810400    orr r0, r1, r0, lsl #8
   4:   e12fff1e    bx  lr

pas de surprise. Bien que l'optimiseur ait laissé cette instruction supplémentaire, ne pouvez-vous pas utiliser ldi sur r19? (Je connaissais la réponse quand je l'ai posée).

EDIT2

pour avr

avr-gcc --version
avr-gcc (GCC) 4.9.2
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

pour éviter la mauvaise habitude ou pas la comparaison 8 bits

arm-none-eabi-gcc --version
arm-none-eabi-gcc (GCC) 7.2.0
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

il est clair que l'optimisation était activée ne prend qu'une seconde pour essayer avec votre propre compilateur pour voir comment il se compare à ma sortie, mais de toute façon:

whatever-gcc -O2 -c so.c -o so.o
whatever-objdump -D so.o

Et oui, utiliser des octets pour des variables de taille octet, certainement sur un avr, pic, etc., vous fera économiser de la mémoire et vous voulez vraiment essayer de le conserver ... si vous l'utilisez réellement, mais comme indiqué ici le moins possible est va être en mémoire, autant dans les registres que possible, donc les économies de flash viennent en n'ayant pas de variables supplémentaires, les économies de RAM peuvent être réelles ou non.

old_timer
la source
2
"Les compilateurs n'avaient pas l'habitude de faire cela, je ne sais pas quelle version a déclenché ce début, je l'ai rencontré tôt dans ma carrière et malgré les compilateurs qui faisaient la promotion dans le désordre (comme ci-dessus), faisant la promotion même si je lui ai dit de faire des mathématiques uchar, pas surpris." C'est parce que les compilateurs C des systèmes embarqués avaient une horrible conformité aux normes :) Le compilateur est généralement autorisé à optimiser, mais ici, il ne peut pas déduire que le résultat tiendra dans un unsigned chardonc il doit effectuer la promotion en 16 bits, comme requis par la norme.
Lundin
1
@old_timer (a<<8)|best toujours incorrect pour tout système avec int16 bits. asera implicitement promu intauquel est signé. En cas atenu une valeur dans le MSB, vous finissez par décalage que les données dans le bit de signe d'un nombre de 16 bits, qui invoque un comportement non défini.
Lundin
1
fun3 est fun..ny ... totalement non optimisé par le compilateur ... Considéré que r1 est toujours 0 dans GCC, et indiquant ra, rb, {rh, rl} les registres des variables a, b et le résultat, le compilateur aurait pu faire: 1) mov rh, r1; 2) mov rl, ra; 2) ajoutez rl, rb; 3) adc rh, rh; 4) ret. 4 Instructions, vs 7 ou 8 ... L'instruction 1 peut être modifiée en ldi rh, 0.
next-hack
1
Ce serait une meilleure réponse si elle spécifiait le compilateur et les options pertinentes utilisées.
Russell Borogove
1
C'est une bonne idée d'éviter d'utiliser int / char etc. et d'utiliser à la place les int16_t et int8_t beaucoup plus explicites et lisibles.
utilisateur
7

Pas nécessairement, car les compilateurs modernes font un bon travail pour optimiser le code généré. Par exemple, si vous écrivez z = x + y;où se trouvent toutes les variables unsigned char, le compilateur doit les promouvoir enunsigned int avant d'effectuer les calculs. Cependant, puisque le résultat final sera exactement le même sans la promotion, le compilateur générera du code qui ajoute simplement des variables 8 bits.

Bien sûr, ce n'est pas toujours le cas, par exemple le résultat de z = (x + y)/2;dépendra de l'octet supérieur, donc la promotion aura lieu. Il peut encore être évité sans recourir à l'assemblage en redirigeant le résultat intermédiaire vers unsigned char.

Certaines de ces inefficacités peuvent être évitées en utilisant les options du compilateur. Par exemple, de nombreux compilateurs 8 bits ont un pragma ou un commutateur de ligne de commande pour adapter les types d'énumération à 1 octet, au lieu de intcelui requis par C.

Dmitry Grigoryev
la source
4
msgstr "le compilateur est requis pour les promouvoir en entier non signé". Non, le compilateur est nécessaire pour les promouvoir int, car il charn'aura probablement pas le même rang de conversion que intsur n'importe quelle plate-forme.
Lundin
3
"Par exemple, de nombreux compilateurs 8 bits ont un pragma ou un commutateur de ligne de commande pour adapter les types d'énumération à 1 octet, au lieu de l'int comme requis par C." La norme C permet d'allouer des variables d'énumération sur 1 octet. Cela nécessite seulement que les constantes d'énumération soient int(oui, c'est incohérent). C11 6.7.2.2Each enumerated type shall be compatible with char, a signed integer type, or an unsigned integer type. The choice of type is implementation-defined...
Lundin