Logiciel de Base : examen de deuxième session ENSIMAG 1A Année scolaire 2009–2010 Consignes générales : – Durée : 1h. Tous documents et calculatrices autorisés. – Vous serez noté sur la précision de vos réponses : on attend des réponses courtes donnant exactement l’information demandée. – Barème indicatif : chaque question sur 2 points. Consignes relatives à l’écriture de code C et assembleur Pentium : – Pour chaque question, une partie des points sera affectée à la clarté du code et au respect des consignes ci-dessous. – Pour les questions portant sur la traduction d’une fonction C en assembleur, on demande d’indiquer en commentaire chaque ligne du programme C original avant d’écrire les instructions assembleur correspondantes. – Pour améliorer la lisibilité du code assembleur, on utilisera systématiquement des constantes (directives .set ou .equ) pour les déplacements relatifs à %ebp (i.e. paramètres des fonctions et variables locales). Par exemple, si une variable locale s’appelle var en langage C, on y fera référence avec var(%ebp). – Sauf indication contraire dans l’énoncé, on demande de traduire le code C en assembleur de façon systématique, sans chercher à faire la moindre optimisation : en particulier, on stockera les variables locales dans la pile (pas dans des registres), comme le fait le compilateur C par défaut. – On respectera les conventions de gestions des registres Intel vues en cours, c’est à dire : – %eax, %ecx et %edx sont des registres scratch ; – %ebx, %esi et %edi ne sont pas des registres scratch. Question 1 Comment sont représentées en mémoire les chaînes de caractères en C ? A quoi correspond ch dans l’exemple suivant : char *ch = "toto" ? En C les chaînes sont des tableaux de caractères terminés par le caractère ’\0’. ch est un pointeur sur le premier caractère de la chaîne. Question 2 Comment est représentée une chaîne vide en C ? Quelle est la différence avec une chaîne "nulle" ? Une chaîne vide est un tableau d’un seul caractère : ’\0’. Une chaîne nulle est représentée par un pointeur NULL. 1 Question 3 Écrire en C une fonction unsigned taille(char *ch) qui renvoie la taille de la chaîne non nulle ch. unsigned taille(char *ch) { unsigned n; for (n = 0; ch[n] != ’\0’; n++); return n; } Question 4 Traduire la fonction taille de la question précédente en assembleur Pentium. On demande une traduction systématique sans chercher à optimiser le code. taille: .set ch, 8 .set n, -4 enter $4, $0 // n = 0 movl $0, n(%ebp) for_taille: // ch[n] != ’\0’ movl n(%ebp), %eax movl ch(%ebp), %edx cmpb $0, (%edx, %eax) je fin_for_taille // n++ addl $1, n(%ebp) jmp for_taille fin_for_taille: // return n movl n(%ebp), %eax leave ret Question 5 Écrire en C une fonction char *concat(char *dst, char *src) qui renvoie la chaîne résultant de la concaténation de src à la fin de dst. Le contenu de la chaîne résultat est stocké dans dst, et on suppose que l’espace réservé pour dst est suffisant pour contenir la concaténation des deux chaînes (il n’y a donc pas besoin d’allouer de mémoire). Les chaînes src et dst ne sont pas nulles. char *concat(char *dst, char *src) { unsigned n = taille(dst); 2 for (unsigned i = 0; src[i] != ’\0’; i++, n++) dst[n] = src[i]; dst[n] = ’\0’; return dst; } Question 6 Traduire la fonction concat de la question précédente en assembleur Pentium. On demande une traduction systématique sans chercher à optimiser le code. concat: .set dst, 8 .set src, 12 .set n, -4 .set i, -8 enter $8, $0 // n = taille(dst) pushl dst(%ebp) call taille addl $4, %esp movl %eax, n(%ebp) // i = 0 movl $0, i(%ebp) for_concat: // src[i] != ’\0’ movl src(%ebp), %edx movl i(%ebp), %ecx cmpb $0, (%edx, %ecx) je fin_for_concat // dst[n] = src[i] movl src(%ebp), %edx movl i(%ebp), %ecx movb (%edx, %ecx), %al movl dst(%ebp), %edx movl n(%ebp), %ecx movb %al, (%edx, %ecx) // i++ addl $1, i(%ebp) // n++ addl $1, n(%ebp) jmp for_concat fin_for_concat: // dst[n] = ’\0’ movl dst(%ebp), %edx movl n(%ebp), %ecx movb $0, (%edx, %ecx) // return dst movl dst(%ebp), %eax leave ret 3 Soit un exemple d’utilisation de la fonction concat : ... char *dst = malloc(1000); strcpy(dst, "titi"); // copie "titi" dans dst char *res = concat(dst, "toto"); ... Question 7 Comment s’appelle la zone mémoire où sont alloués les 1000 octets réservés pour dst par la fonction malloc ? Où est stockée la chaîne "toto" lors de l’exécution du programme ? malloc alloue dans le tas (heap en anglais). Les constantes de type chaîne (ou autre) sont stockées dans la zone data. Question 8 Comment sont passés les paramètres à la fonction concat ? Plus précisément, on veut savoir ce qui est copié dans la pile juste avant d’exécuter call concat. On copie dans la pile les adresses des chaînes (pas leur contenu !). Pour dst, c’est l’adresse de début de la zone réservée dans le tas qui est passée. Pour "toto", c’est l’adresse du premier caractère de la constante dans la zone data. Question 9 Expliquer en quelques lignes ce que fait le code assembleur (optimisé) ci-dessous. // void fct(int tab[]) fct: .set tab, 8 enter $0, $0 pushl %ebx movl $0, %ecx movl $0, %edx movl tab(%ebp), %ebx etiq1: cmpl $0, (%ebx, %ecx, 4) je etiq2 jl etiq3 movl (%ebx, %ecx, 4), %eax movl %eax, (%ebx, %edx, 4) addl $1, %edx etiq3: addl $1, %ecx jmp etiq1 etiq2: movl $0, (%ebx, %edx, 4) popl %ebx leave ret Cette fonction supprime les entiers strictement négatifs du tableau d’entiers signés passé en paramètre. Le tableau est terminé par l’entier 0. 4 Question 10 Pourquoi copie-t’on le registre %ebx dans la pile au début de cette fonction fct ? Le registre %ebx n’est pas un registre scratch : on doit donc le sauvegarder avant de l’utiliser dans fct, car la fonction appelante a pu laisser des valeurs importantes dedans. Question 11 Donner le code C équivalent à cette fonction fct. void fct(int tab[]) { int i, j; for (j = i = 0; tab[i] != 0; i++) if (tab[i] > 0) tab[j++] = tab[i]; tab[j] = 0; } 5