samedi 15 novembre 2008

Les arguments de ligne de commande

Je me sert très souvent des arguments de la ligne de commande pour ne pas dire toujour ...
Regardons le bout de code ci dessous:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
printf("adresse de argc: %p\n"
"valeur de argc: %d\n"
"[ valeur gauche de argv: %p | "
"valeur droite de argv : %p ]\n", &argc, argc, &argv, argv);

return EXIT_SUCCESS;

}

Ou argc est égal au nombre d' arguments de la ligne de commande -1 car argv[0] contient le nom du programme donc argc vaut toujour 1, et argv est pointeur sur un tableau de pointeur qui pointe sur l' adresse en mémoire du premier caractère de chaque argument.
Le shema ci-dessous résume la situtation très simplement:

Si je lance le programme au dessus en le compilant avec l' option -g pour pouvoir ensuite le debugger j' obtient ceci :
$ ./a.out arg1 arg2
adresse de argc: 0xbfcd42e0
valeur de argc: 3
[ valeur gauche de argv: 0xbfcd42e4 | valeur droite de argv : 0xbfcd4364 ]

Ici il y a plusieur chose interressante:
1) on remarque que argc et argv ce suivent en mémoire, argc est un entier codé sur 4 octects et argv est un pointeur qui lui aussi est codé sur 4 octets.
Donc 0xbfcd42e4 - 0xbfcd42e0 = 0x4.
0xbfcd42e4 n' est pas l' adresse du premier argument mais bien l' adresse du pointeur qui pointe sur l' adresse 0xbfcd4364 qui est l' adresse du tableau de pointeur.

Attention: Les adresses qu' on voit ici ne sont pas les adresses exact comme elle sont dans la mémoire physique de la machine, ce sont des adresses virtuelles, un mécanisme de translation de ces adresses virtuelles en adresse physique est mis en place par le système et d' autre par, par ce que l' on apel la MMU (Memory Management Unit) j' èspère pouvoir faire un article la dessus.
Sans rentrer dans les détails quand un programme tourne il dispose d' un espace d' adressage a lui tout seul, avec son code, ses données, et plein d' autre informations, en théorie il est donc impossible qu' un autre programme puisse écrire quoi que ce soit dans l' espace d' adressage d' un autre.


Revenons en à nos moutons et allons chercher dans cette espace d' adressage les données ou elles ce trouvent avec gdb :
$ gdb -q ./a.out
1 . (gdb) b main
2 . Breakpoint 1 at 0x8048385: file test.c, line 7.
3 . (gdb) r arg1 arg2
4 . Starting program: /home/napoleon/a.out arg1 arg2
5 .
6 . Breakpoint 1, main (argc=3, argv=0xbfd9abe4) at test.c:7
7 . 7 printf("adresse de argc: %p\n"
8 . (gdb) p &argc
9 . $1 = (int *) 0xbfd9ab60
10 . (gdb) p &argv
11 . $2 = (char ***) 0xbfd9ab64
12 . (gdb) p argv
13 . $3 = (char **) 0xbfd9abe4
14 . (gdb) x/4wx $3
15 . 0xbfd9abe4: 0xbfd9c790 0xbfd9c7a5 0xbfd9c7aa 0x00000000
16 . (gdb) x/s 0xbfd9c790
17 . 0xbfd9c790: "/home/napoleon/a.out"
18 . (gdb) x/s 0xbfd9c7a5
19 . 0xbfd9c7a5: "arg1"
20 . (gdb) x/s 0xbfd9c7aa
21 . 0xbfd9c7aa: "arg2"

On voit que les adresses on changer ce qui est normal, mais le principe est le même.

A la ligne 8 j' affiche l' adresse de argc, puis a la ligne 10 j' affiche l' adresse du pointeur argv, encore une fois on voit bien qu' il ce suivent.
a la ligne 12 j' affiche l' adresse pointé par argv avec la commande 'p' qui est le raccourci de 'print' ce qui a pour effet de placer le résultat de la commande dans la variable $3 (ligne 13).
Juste après a la ligne 14 j' affiche un morceau de la mémoire avec la commande 'x'. ici je fait suivre x/ par 4wx ce qui veut dire affiche moi 4 mots mémoire en hexadécimal de l' adresse contenu dans la variable $3.
Et la a la ligne 15 j' obtient l' adresse du premier octet de chacun de mes 3 arguments + le NULL qui doit obligatoirement terminer le tableau, la aussi on remarque facilement que les trois arguments ce suivent, les lignes suivante j' ai plus qu' a afficher la chaine contenu a partir de chaque adresse, avec la commande x/s ou le 's' veut dire string.

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
int i = 1;
while(i < argc)
{
printf("argv[%d] = [ %s ] a l' adresse %p\n", i, argv[i], argv[i]);
i++;
}
return EXIT_SUCCESS;
}

$ ./a.out arg1 arg2 arg3
argv[1] = [ arg1 ] a l' adresse 0xbfb7e76b
argv[2] = [ arg2 ] a l' adresse 0xbfb7e770
argv[3] = [ arg3 ] a l' adresse 0xbfb7e775


En réalité on ne va pas analyser les options de la ligne de commande de cette façon qui est lourde et chiante a codé (imaginer le nombre de comparaison a faire etc ...) , pour cela il y a la fonction getopt() défini dans /usr/include/unistd.h et getopt.h pour les options long.

Aucun commentaire: