Du C à l'assemblage


16

Supposons que nous ayons le morceau de code C suivant pour un avr-8bit:

int v1=1;
int v2=2;
v2=v2+v1;

Je m'attendais à ce que le démontage suivant

ldi r18, 1;
ldi r19, 2;
add r19, r18;

mais après avoir couru:

avr-gcc -mmcu=atmega2560 Test.c -o Test.elf

et

avr-objdump -S Test.elf > Test.lss

J'ai le démontage suivant

    ldi r24, 0x01   ; 1
    ldi r25, 0x00   ; 0
    std Y+2, r25    ; 0x02
    std Y+1, r24    ; 0x01
    ldi r24, 0x02   ; 2
    ldi r25, 0x00   ; 0
    std Y+4, r25    ; 0x04
    std Y+3, r24    ; 0x03
    ldd r18, Y+3    ; 0x03
    ldd r19, Y+4    ; 0x04
    ldd r24, Y+1    ; 0x01
    ldd r25, Y+2    ; 0x02
    add r24, r18
    adc r25, r19
    std Y+4, r25    ; 0x04
    std Y+3, r24    ; 0x03

y a-t-il quelqu'un qui peut m'aider à comprendre le résultat du démonteur?

Edit: En utilisant char, l'assemblage devient:

ldi r24, 0x01
std Y+1, r24
ldi r24, 0x02
std Y+2, r24
ldd r25, Y+2
ldd r24, Y+1
add r24, r25
std Y+2, r24

Quand y a-t-il une instruction standard?

Réponses:


20

Réponse courte: vos registres sont à 8 bits et vos valeurs à 16 bits. Il s'agit donc de les manipuler en deux morceaux.

Longue réponse:

    ldi r24, 0x01   ; 1
    ldi r25, 0x00   ; 0

Stockez la valeur 16 bits 1 dans les registres 8 bits r24, r25.

    std Y+2, r25    ; 0x02
    std Y+1, r24    ; 0x01

Stockez-le aux emplacements de pile Y + 1, Y + 2.

    ldi r24, 0x02   ; 2
    ldi r25, 0x00   ; 0

Stockez la valeur 16 bits 2 dans les registres 8 bits r24, r25.

    std Y+4, r25    ; 0x04
    std Y+3, r24    ; 0x03

Stockez-le aux emplacements de pile Y + 3, Y + 4.

    ldd r18, Y+3    ; 0x03
    ldd r19, Y+4    ; 0x04
    ldd r24, Y+1    ; 0x01
    ldd r25, Y+2    ; 0x02

Copiez-les de la pile vers (r18, r19) et (r24, r25)

    add r24, r18
    adc r25, r19

Ajouter (r18, r19) à (r24, r25), y compris le deuxième ajout

    std Y+4, r25    ; 0x04
    std Y+3, r24    ; 0x03

Stockez-le sur la pile.

Pour obtenir votre assemblage d'origine, essayez deux choses:

  • utiliser des variables "char"
  • utiliser l'option du compilateur "-O2"

Edit : la raison pour laquelle le compilateur stocke les variables dans la pile plutôt que de les conserver dans les registres est parce qu'elles sont stockées avec le type de stockage "auto" par défaut. Il peut les optimiser dans des registres, mais ce n'est pas obligatoire, même si vous les déclarez avec la classe de stockage "register".

Bien que ce ne soit pas une exigence stricte du langage, c'est un comportement normal du compilateur. Si, à un moment donné, vous prenez l'adresse de v1, vous devez lui attribuer un emplacement de stockage et le sauvegarder à chaque fois que la valeur de "v1" change. Donc, pour économiser la comptabilité de savoir si v1 doit ou non être stocké dans un registre ou sur la pile, il le conserve sur la pile et traite chaque ligne de code séparément.


Je vous remercie! C'est plus clair maintenant! Veuillez trouver ma modification dans la question.
DarkCoffee

1
Voir mon montage. Essayez également -O2. Peut-être -O3, bien que cela puisse produire du code cassé.
pjc50

3
Beaucoup de code intégré avec lequel je travaille définit des types supplémentaires qui sont spécifiques à leur taille, tels que "uint8, uint16, uint32" pour les entiers non signés, par exemple. De cette façon, vous savez toujours exactement à quel type de variable vous avez affaire. Surtout dans les petits imbriqués, signés, flottants, les "int" de taille / signature indéfinies vous coûteront au mieux des cycles de processeur et, au pire, causeront de graves bogues.
John U

Les vrais compilateurs ont cessé de se comporter comme ça il y a environ 10-15 ans. Le problème d'allocation des registres est principalement résolu et les compilateurs sont sacrément bons dans ce domaine. Ils savent exactement quand une variable doit être sur la pile et quand elle peut être dans un registre, si cela vaut la peine de la déplacer et quand le faire. La comptabilité est effectuée au moment de la compilation et les compilateurs eux-mêmes disposent de gigaoctets de mémoire. La grande exception est le mode débogage, pour des raisons évidentes, mais tout est sur la pile.
MSalters

@ pjc50 -O3peut produire du code cassé? [citation nécessaire] (et non, le code C qui invoque un comportement
indéfini

4

Comme j'ai trouvé un exemple de code, je ferai de mon commentaire une réponse - d'autres ont déjà expliqué le problème.

Beaucoup de code intégré avec lequel je travaille définit des types supplémentaires qui sont spécifiques à leur taille, tels que "uint8, uint16, uint32" pour les entiers non signés, par exemple. De cette façon, vous savez toujours exactement à quel type de variable vous avez affaire. Surtout dans les petits imbriqués, signés, flottants, les "int" de taille / signature indéfinies vous coûteront au mieux des cycles de processeur et, au pire, causeront de graves bogues.

Voici nos # définitions actuelles:

/*
 * Example - the basic data types from our embedded code
 */
typedef unsigned char       uint8;  /*  8 bits */
typedef unsigned short int  uint16; /* 16 bits */
typedef unsigned long int   uint32; /* 32 bits */

typedef char                int8;   /*  8 bits */
typedef short int           int16;  /* 16 bits */
typedef int                 int32;  /* 32 bits */

typedef volatile int8       vint8;  /*  8 bits */
typedef volatile int16      vint16; /* 16 bits */
typedef volatile int32      vint32; /* 32 bits */

typedef volatile uint8      vuint8;  /*  8 bits */
typedef volatile uint16     vuint16; /* 16 bits */
typedef volatile uint32     vuint32; /* 32 bits */

3
Bonne idée; uint8_t et ses amis font désormais partie de la norme: stackoverflow.com/questions/16937459/…
pjc50

Comme c'est pratique! Nous avons hérité de ceux qui avaient un projet qui était C89, donc il est bon de savoir qu'il existe une version officielle.
John U

2

Votre code C utilise des variables entières 16 bits (int). Le compilateur ne peut pas lire dans vos pensées, il compile donc exactement ce qui se trouve dans le fichier source. Donc, si vous voulez des variables 8 bits, vous devez utiliser le type respectif.

En conséquence, vous obtiendrez toujours le stockage des valeurs dans la mémoire (bien que plus simple). Je ne suis pas si bon en C, mais à mon humble avis, il y a quelques options pour assigner la variable à un registre, si vous voulez que certaines variables soient dans les registres au lieu de RAM. Quelque chose comme:

register unsigned char VARNAME asm("r3");

Notez que tous les registres ne sont pas disponibles pour de telles astuces.

Alors, la conclusion? Écrivez vos programmes en assembleur. Ils seront toujours plus petits, plus rapides et faciles à lire / à soutenir.


L'assemblage est plus facile à lire que C?
dext0rb

@ dext0rb - Oui. Bien sûr, si vous les connaissez assez bien. Si vous ne connaissez que le C, l'assemblage et tous les autres langages seront difficiles à lire.
johnfound

Je dois être en désaccord avec le dernier point, les programmes écrits en assembleur sont beaucoup plus difficiles à lire. Comparez simplement le code source donné ci-dessus. Le code C est beaucoup plus clair et plus court, ainsi que son intention. Cette différence ne fait qu'augmenter lorsque des structures sont utilisées.
soandos

@soandos - Le code C est plus court, oui. Plus clair? Je ne suis pas sûr. S'il en était ainsi, la question ci-dessus n'aurait pas du tout à être posée. En fait, le prix de la "brièveté" est le "flou" des détails.
johnfound

Bien sûr, le gars qui dit "Je ne suis pas si bon en C" va proclamer les vertus du pur assemblage. : D
dext0rb
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.