IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

x86 32-bits / 64-bits Assembleur Discussion :

Besoin d'explication sur une sortie de g++ en assembleur


Sujet :

x86 32-bits / 64-bits Assembleur

  1. #1
    Membre habitué
    Profil pro
    Inscrit en
    Mai 2010
    Messages
    280
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2010
    Messages : 280
    Points : 149
    Points
    149
    Par défaut Besoin d'explication sur une sortie de g++ en assembleur
    Bonjour,

    Le programme en assembleur suivant :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
     
    _Z1fi:
    .LFB0:
    	.cfi_startproc
    	pushq	%rbp
    	.cfi_def_cfa_offset 16
    	.cfi_offset 6, -16
    	movq	%rsp, %rbp
    	.cfi_def_cfa_register 6
    	movl	%edi, -4(%rbp)
    	addl	$1, -4(%rbp)
    	movl	-4(%rbp), %eax
    	popq	%rbp
    	.cfi_def_cfa 7, 8
    	ret
    	.cfi_endproc
    .LFE0:
    	.size	_Z1fi, .-_Z1fi
    	.globl	main
    	.type	main, @function
    main:
    .LFB1:
    	.cfi_startproc
    	pushq	%rbp
    	.cfi_def_cfa_offset 16
    	.cfi_offset 6, -16
    	movq	%rsp, %rbp
    	.cfi_def_cfa_register 6
    	subq	$16, %rsp
    	movl	$1, -4(%rbp)
    	movl	-4(%rbp), %eax
    	movl	%eax, %edi
    	call	_Z1fi
    	movl	%eax, -4(%rbp)
    	movl	$0, %eax
    	leave
    	.cfi_def_cfa 7, 8
    	ret
    	.cfi_endproc
    correspond à la sortie de g++ en assembleur du programme suivant en c++ :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    int f(int x){return ++x;}
     
    int main(){
    	int x=1;
    	x=f(x);
    }
    Je ne comprends pas pourquoi le programme effectue la copie dans deux registre de la valeur de x et place de plus cette valeur sur la pile :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    # là
    	movl	$1, -4(%rbp)
    # là
    	movl	-4(%rbp), %eax
    # et là
    	movl	%eax, %edi
    d'autant que la fonction f ne semble pas utiliser la valeur passer par la pile et utilise à la place le contenu de edi :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    	movl	%edi, -4(%rbp)
    	addl	$1, -4(%rbp)
    Quelqu'un a-t-il une explication ?
    "Bien qu'on ait du coeur à l'ouvrage,
    L'Art est long et le Temps est court." - CB

  2. #2
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Septembre 2007
    Messages
    7 368
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 368
    Points : 23 620
    Points
    23 620
    Par défaut
    Bonjour,

    Tout d'abord, il faut savoir qu'en notation A&AT (celle produite par défaut par GCC et à l'opposée de la syntaxe Intel habituelle) place l'opérande source avant la destination : « mov source, dest ».

    Ensuite, la pile est utilisée à tout et n'importe quoi, mais principalement à ces choses-là :
    • Au niveau de l'assembleur, elle sert à sauvegarder des registres temporairement et à enregistrer les adresses de retour lors de CALL ou d'interruptions, logicielles comme matérielles ;
    • Au niveau du C, le compilateur s'en sert pour définir les cadres de pile, passer les arguments à une fonction et stocker les variables locales. C'est le moyen le plus facile pour allouer de la mémoire rapidement, spécialement lorsque tu utilises des fonctions récursives, et c'est aussi pour cela qu'elles disparaissent une fois que tu as quitté la fonction.


    L'accumulateur A (donc « AX » sur Intel et ses déclinaisons AL, AH, EAX et RAX) est le seul registre dont tu es à peu près sûr qu'il soit disponible sur tous les micro-processeurs et qu'il soit accessible par l'ALU, et c'est à cela qu'il sert initialement (un peu comme l'écran d'une calculatrice quatre opérations qui sert à la fois à entrer la donnée et à recevoir le résultat de l'opération). Donc, dans cet esprit et sauf dispositions contraires, RAX est utilisé par défaut pour recevoir le résultat d'une évaluation.

    C'est aussi lui qui contiendra la valeur finale d'une fonction. En C et C++, on t'oblige en principe à utiliser « return » mais certains langages comme le Perl utilise la dernière valeur évaluée. C'est pour cela qu'on voit parfois une constante toute seule sur une ligne à la fin d'un programme.

    D'autre part, si la manière normale d'exécuter une fonction est donc de passer les arguments sur la pile et d'en recevoir le résultat dans EAX par défaut, il est possible de demander au compilateur de passer les premiers arguments dans des registres lorsqu'ils sont disponibles, et le reste dans pile, normalement. C'est intéressant parce que cela permet d'exploiter les ressources à disposition, d'éviter des transactions mémoire (et donc d'accélérer notablement les choses), et d'économiser la pile, spécialement dans le cas des fonctions récursives, encore une fois.

    Donc, fort de ces informations :
    • movl $1, -4(%rbp) met la valeur « 1 » dans la pile. Ça correspond à l'initialisation de ta variable : « int x=1; » ;
    • movl -4(%rbp), %eax met la valeur de « x » dans EAX. Ça correspond à l'évaluation de l'expression précédente. L'affectation d'une valeur à une variable est une expression qui peut être évaluée, qui correspond à la valeur affectée et qui permet donc d'écrire des choses comme « x=y=1 » ;
    • movl %eax, %edi correspond au passage de « x », en paramètre de ta fonction, avec optimisation puisque l'on transmet ce paramètre directement dans un registre plutôt que par la pile.


    À noter que dans ce dernier cas, on aurait pu directement utiliser EAX, ce qui en plus aurait bien collé à la philosophie d'accumulateur de ce registre. Mais il arrive que les fonctions aient besoin d'un registre à tout faire en entrant dans la fonction et, surtout, la manière dont les registres vont être utilisés lors de ces optimisations doit nécessairement être normalisé.

    Notons enfin que si on ne s'intéresse qu'à la valeur d'une valeur et que l'on n'a aucune intention de s'y référer par adresse (en passant un pointeur dessus à quelque chose), on pouvait utiliser explicitement le mot-clé « register » devant la déclaration d'une variable. Ce mot-clé est cependant tombé en désuétude car il ne s'agit que d'une indication au compilateur, qui peut l'ignorer si quelqu'un récupère le pointeur de la variable ou s'il n'y a pas assez de registres disponibles et, a contrario, peut se permettre de faire directement l'optimisation même sans indication du programmeur s'il sait que cette variable ne sera jamais atteinte autrement que par la fonction concernée et sans utiliser son pointeur.

  3. #3
    Membre habitué
    Profil pro
    Inscrit en
    Mai 2010
    Messages
    280
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2010
    Messages : 280
    Points : 149
    Points
    149
    Par défaut
    Bonjour et merci pour cette réponse détaillée,

    Citation Envoyé par Obsidian Voir le message
    Donc, fort de ces informations :
    • movl $1, -4(%rbp) met la valeur « 1 » dans la pile. Ça correspond à l'initialisation de ta variable : « int x=1; » ;
    • movl -4(%rbp), %eax met la valeur de « x » dans EAX. Ça correspond à l'évaluation de l'expression précédente. L'affectation d'une valeur à une variable est une expression qui peut être évaluée, qui correspond à la valeur affectée et qui permet donc d'écrire des choses comme « x=y=1 » ;
    • movl %eax, %edi correspond au passage de « x », en paramètre de ta fonction, avec optimisation puisque l'on transmet ce paramètre directement dans un registre plutôt que par la pile.


    À noter que dans ce dernier cas, on aurait pu directement utiliser EAX, ce qui en plus aurait bien collé à la philosophie d'accumulateur de ce registre. Mais il arrive que les fonctions aient besoin d'un registre à tout faire en entrant dans la fonction et, surtout, la manière dont les registres vont être utilisés lors de ces optimisations doit nécessairement être normalisé.
    Oui, il est évident qu'un passage par registre prend moins de temps aussi pourquoi ne pas faire depuis le main directement movl $1, %edi et récupéré dans la fonction la valeur de edi ? Je comprends que le compilateur puisse utiliser plusieurs "options" de passage d'argument quand la fonction appelée ne fait pas partie de la même unité de compilation mais là le compilateur à toutes les informations nécessaire pour choisir un passage par pile ou par registre.

    edit : après je comprends que l'automatisation de la traduction du code source en code machine par un programme puisse générer des comportement automatique de la part du compilateur. Peut-être s'agit il de cela ?

    re edit: de plus il est nécessaire de stocker en mémoire et non pas de la laisser en registre la valeur de x. Donc l'écriture de 1 sur la pile se justifie. Ce que je ne comprend pas c'est le passage par eax alors que movl -4(%rbp), %edi aurait suffit...
    "Bien qu'on ait du coeur à l'ouvrage,
    L'Art est long et le Temps est court." - CB

  4. #4
    Membre habitué
    Profil pro
    Inscrit en
    Mai 2010
    Messages
    280
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2010
    Messages : 280
    Points : 149
    Points
    149
    Par défaut
    ah je crois que j'ai compris ce que tu veux dire par évaluer en plaçant la valeur dans eax on peut évaluer x=1 par le résultat de cette opération sur le registre d'état (?)

    ce que ne permet peut-être pas (movl $1, -4(%rbp) (?)
    "Bien qu'on ait du coeur à l'ouvrage,
    L'Art est long et le Temps est court." - CB

  5. #5
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Septembre 2007
    Messages
    7 368
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 368
    Points : 23 620
    Points
    23 620
    Par défaut
    Voila. C'est tout cela à la fois.

    Pour le reste, bien des optimisations peuvent être faites mais cela demande une heuristique qui consiste à analyser la fonction entière. On ne sait pas où se trouve « l'horizon » du compilateur en ce domaine.

    Par contre, il serait intéressant que tu passes des options d'optimisation explicites telles que « -O3 » à g++ et voir quel genre de code il produit dans ce cas.

  6. #6
    Membre habitué
    Profil pro
    Inscrit en
    Mai 2010
    Messages
    280
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2010
    Messages : 280
    Points : 149
    Points
    149
    Par défaut
    Par contre, il serait intéressant que tu passes des options d'optimisation explicites telles que « -O3 » à g++ et voir quel genre de code il produit dans ce cas.
    En effet en faisant une optimisation maximale ce n'est plus la même chose :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    	.file	"exo10.cpp"
    	.text
    	.p2align 4,,15
    	.globl	_Z1fi
    	.type	_Z1fi, @function
    _Z1fi:
    .LFB0:
    	.cfi_startproc
    	leal	1(%rdi), %eax
    	ret
    	.cfi_endproc
    .LFE0:
    	.size	_Z1fi, .-_Z1fi
    	.section	.text.startup,"ax",@progbits
    	.p2align 4,,15
    	.globl	main
    	.type	main, @function
    main:
    .LFB1:
    	.cfi_startproc
    	xorl	%eax, %eax
    	ret
    	.cfi_endproc
    .LFE1:
    	.size	main, .-main
    	.ident	"GCC: (SUSE Linux) 4.6.2"
    	.section	.comment.SUSE.OPTs,"MS",@progbits,1
    	.string	"Ospwg"
    	.section	.note.GNU-stack,"",@progbits
    Mise à zéro du registre eax, appelle de la fonction, incrémentation de eax et retour...

    Merci encore pour tes explications

    edit : en fait même pas... la fonction n'est pas appelée...
    "Bien qu'on ait du coeur à l'ouvrage,
    L'Art est long et le Temps est court." - CB

  7. #7
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Septembre 2007
    Messages
    7 368
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 368
    Points : 23 620
    Points
    23 620
    Par défaut
    Citation Envoyé par PyNub Voir le message
    edit : en fait même pas... la fonction n'est pas appelée...
    Non, effectivement. :-)
    Le compilo s'est rendu compte que non seulement tu ne l'appelles qu'une fois, mais tu n'utilise jamais sa valeur de retour, même si tu l'affectes à x. Dans le même esprit, on aurait très bien pu se passer de compiler la fonction elle-même, mais ce n'était pas possible pour plusieurs raisons :

    — Il aurait fallu pousser l'heuristique au delà des frontières de la fonction pour la poursuivre dans la fonction appelée. C'est possible mais ça complique beaucoup les choses ;
    — Une fonction peut avoir des effets de bords même lorsque l'on utilise jamais sa valeur de retour. C'est le cas de toutes les fonctions que l'on utilise comme des commandes ;
    — Le programme compilé, même s'il s'agit d'un exécutable, reste une ressource qui peut être utilisée comme une bibliothèque sous certaines conditions. La fonction se doit donc d'exister quand même pour pouvoir éventuellement être exploitée par un projet externe.

  8. #8
    Membre habitué
    Profil pro
    Inscrit en
    Mai 2010
    Messages
    280
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2010
    Messages : 280
    Points : 149
    Points
    149
    Par défaut
    Oui en effet l'utilisation de la fonction par une autre unité de compilation doit pouvoir être envisagée et cette information est hors de portée du compilateur.

    L'optimisation permet dans cette exemple d'économiser plusieurs accès mémoire. Autant dire que les options d'optimisation de gcc sont indispensable à la création d'un programme efficace.

    En fait la compilation sans optimisation équivaut à une traduction "mot à mot" du programme source.

    Merci pour toutes ces précisions.
    "Bien qu'on ait du coeur à l'ouvrage,
    L'Art est long et le Temps est court." - CB

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. [Débutant] Un noob a besoin d'une explication sur une instruction curieuse
    Par Laskar dans le forum C#
    Réponses: 4
    Dernier message: 09/04/2014, 09h26
  2. Besoin d'explication sur une question de syntaxe
    Par Cynthia. dans le forum Mise en page CSS
    Réponses: 2
    Dernier message: 25/10/2010, 17h22
  3. Besoin d'une explication sur une ligne !
    Par info3licen dans le forum Général JavaScript
    Réponses: 2
    Dernier message: 04/03/2010, 19h45
  4. Besoin d'une explication sur une "notation" de vista
    Par honeydew dans le forum Windows Vista
    Réponses: 5
    Dernier message: 22/08/2008, 17h35
  5. Besoin d'explication sur une requête.
    Par xtiand4 dans le forum SAP
    Réponses: 3
    Dernier message: 09/07/2008, 10h48

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo