Jump to content
Sign in to follow this  
AlexMog

[Cours #5] ArgC, ArgV et allocations Dynamiques

Recommended Posts

Bonjour à tous,
Bienvenue dans ce 4ième cours sur la programmation en C.
Aujourd'hui, nous allons voir un point intéréssent de notre programme, et un point clé en C: les
Arguments de lancement, et les allocations dynamiques.

I- Qu'est-ce qu'un argument de lancement?
Un argument de lancement est une string ajoutée au lancement du programme permettant de faire passer ladite string au programme (string = chaine de caractères).
Ainsi, si je lance le programme avec cette ligne de commande:

mon_programme mon_argument

 

mon programme recevra comme argument 1 : "mon_argument".
(En faite, d'une façon générale, les arguments sont gérés par les shells qui lancent le programme. C'est une convention de programmation).
Quelques exemples d'utilisations d'arguments:
Les commandes unix utilisent très souvent les arguments:

ls --help
cd --
cd /home
cd ~
mkdir test
...

 

et peuvent donc permettre de faire passer une valeur, ou même un chemin à son programme (bah oui, c'est des Strings (tableaux de caractères). Bon, passons à la pratique:

II- Récupérer des arguments de lancement
Nous avions vu la structure basique d'un main en C:

int main(void)
{
}

Eh bien, je ne vous ai pas dit la vérité sur cette fonction!
En effet, main peut prendre un void pour argument (et donc ne prendre aucun arguments), ou bien prendre un int, et un char **.
Le véritable prototypage de la fonction main est donc:

int main(int argc, char **argv)
{
}

En voyant ça, vous allez me dire "dis donc, j'ai mal à la tête, y'a un ** dans ton code!" Eh bien ne
vous inquiétez pas, il s'agit d'un tableau de chaines de caractères.Prenons pour exemple ce programme lancé comme ceci:

coucou arg1 arg2 arg3 je suis fou

Eh bien, nous pouvons récupérer les arguments de ce programme de la façon suivante:

/*
** Je re-crée les fonctions d'affichage. Voir cours #1 et #2
*/
void my_putchar(char c)
{
  write(1, &c, 1);
}

void my_putstr(char *str)
{
  while(*str)
    my_putchar(*(str++));
}

int main(int argc, char **argv)
{
  my_putstr(argv[0]); // Affichera
  my_putstr(argv[1]); // Affichera
  my_putstr(argv[2]); // Affichera
  my_putstr(argv[3]); // Affichera
  my_putstr(argv[4]); // Affichera
  my_putstr(argv[5]); // Affichera
  my_putstr(argv[6]); // Affichera
  return (0);
}
"cocuou"
"arg1"
"arg2"
"arg3"
"je"
"suis"
"foud"

 

Nous butons à présent sur un problème, ré-utilisons le programme précédent, mais supprimons tous,
ou un des arguments:

Segmentation Fault

 

Eh oui, comme vous le voyez, nous essayons d'accéder à la valeur d'un tableau qui n'existe pas, ce qui provoque une erreur d'accès en mémoire.
Pour remédier à cela, les développeurs nous ont rajoutés "argc".
Pour mieux vous faire comprendre, je vais vous définir exactement ce que signifie argc et argv:
ArgC est un couplage entre les mots: Argument et Count, il s'agit donc du nombre d'arguments qui ont été passés en paramètres.
ArgV est un couplage entre les mots: Argument et Value, il s'agit donc des valeurs de ces dits arguments.

Maintenant que vous avez le nombre d'arguments, vous pouvez vérifier que le nombre d'arguments est correcte avant de l'afficher:

int main(int argc, char **argv)
{
  if (argc > 1)
    my_putstr(argv[1]);
  return (0);
}

argv[0] sera toujours présent, puisqu'il s'agit du tout premier argument, qui est vital pour lancer le programme (il s'agit plus exactement de la commande utilisée pour lancer le programme).
Passons à présent à la partie complexe de ce cours: les allocations dynamiques.

III- Les allocations dynamiques
L'allocation dynamique, c'est l'art de savoir utiliser sa RAM et les répercussions de l'alloc sur celle-ci.
Nous avions vu précédemment que pour pouvoir avoir une chaine de caractère avec une certaine taille, nous devions définir un tableau de caractères, avec une taille FIXE.
Comme ceci:

char mon_tableau[talle_de_mon_tableau];

Là est le problème, imaginons que nous devions modifier notre chaine, en lui imposant une taille plus grande: SegFault.
Eh bien, la solution, c'est d'utiliser l'allocation dynamique: Au lieu de stocker notre chaine dans la stack, stockons la dans la RAM! (ce qui nous permet d'avoir plus d'espace, d'ailleurs! (généralement, la stack est limitée à quelques Ko, alors que la RAM non))
Vous comprenez donc qu'il est dangereux d'utiliser malloc de façon idiote: dans une boucle infinie par exemple... Puisque vous allez remplir votre RAM!

Prenons un exemple simple:
J'ai un programme qui connaît la taille d'une chaine de caractère, et je dois la stocker dans une variable:

int main(void)
{
  int lenght = 300000; // Oui, notre chaine est très longue
}

Si je tentais de déclarer un tableau statique de char de taille 300 000, il y aura énormément de chances pour que mon programme segfault dès le lancement.
C'est problématique, en effet.
Tentons donc d’allouer un emplacement dans la ram pour cette chaine:

int main(void)
{
  char *ptr;
  
  ptr = malloc(300000 * sizeof(char));
}

Tiens, ça marche!
Lisons un peu mieux le man de malloc: en cas d'erreur, malloc retourne NULL.
Nous allons donc vérifier si il y a eu une erreur (pour éviter le segfault):

int main(void)
{
  char *ptr;
  ptr = malloc(300000 * sizeof(char));
  if (ptr == NULL)
    {
      my_putstr("Erreur malloc\n");
      return ;
    }
}

Bon, nous avons donc alloué un emplacement de type char* (donc tableau de caractères) dans notre ram, avec une taille de 300 000 cases! Cool!
Nous pouvons donc remplire "ptr" exactement comme un tableau (case par case, voir cours sur les chaines de caractères).
ATTENTION: ptr est un POINTEUR. Si vous modifiez son adresse, vous perdez l'ancienne adresse allouée! Donc ceci:

int main(void)
{
  char *ptr;
  
  ptr = malloc(300000 * sizeof(char));
  if (ptr == NULL)
   {
     my_putstr("Erreur malloc\n");
     return ;
   }
  ptr = "coucou";
}

est interdit et stupide (ne rigolez pas, les profs de programmation font souvent l'erreur!).
Bon, c'est bien, nous avons alloué de la mémoire, mais bon, c 'est pas cool pour la RAM, on ne la vide jamais, il va falloir la vider!
Pour la vider, la fonction "free" est là pour nous aider!
Voilà mon programme une fois la fonction "free" utilisée:

int main(void)
{
  char *ptr;
  
  ptr = malloc(300000 * sizeof(char));
  if (ptr == NULL)
   {
     my_putstr("Erreur malloc\n");
     return ;
   }
  free(ptr);
}

Tout marche! Cool!
ATTENTION: pour bien vous montrer que modifier un pointeur est une mauvaise idée, tentez de faire ceci:

int main(void)
{
  char *ptr;

  ptr = malloc(300000 * sizeof(char));
  if (ptr == NULL)
   {
     my_putstr("Erreur malloc\n");
     return ;
   }
   ptr = "coucou";
   free(ptr);
}

Hop, vous tomberez sur une erreur qui se nomme "glibC" et qui corresponds à une erreur de pointage, la fonction "free" vous hurle dessus comme quoi votre pointeur ne pointe pas sur une zone
allouée.
Pour vous montrer que mon code fonctionne, je vais afficher ma chaine de caractère, qui fera
l'alphabet avec 300 000 lettres!

int main(void)
{
  char *ptr;
  int i;

  i = 0;
  j = 0;
  ptr = malloc(300000 * sizeof(char));
  if (ptr == NULL)
  {
    my_putstr("Erreur malloc\n");
    return ;
  }
  while(i < 300000)
  {
    ptr[i] = j + 'a';
    i = i + 1;
    j = j + 1;
    if (j + 'a' >= 'z')
      j = 0;
  }
  ptr[i] = '\0'; // NE PAS OUBLIER LA FIN DE LA CHAINE (voir cours sur les chaines de caractères)
  my_putstr(ptr);
  free(ptr);
}

Et voilà, vous savez à présent utiliser les allocations dynamiques!
A bientôt pour le prochain cours!
Cours écrit par AlexMog. Contact: alexmog [at] live [point] fr

  • Upvote 2

Share this post


Link to post
Share on other sites

J'adore tes tutoriels : longs (très longs et tant mieux), complets et fournis. :)

Tu mérites tout à fait ton grade.

+1 rep.

Share this post


Link to post
Share on other sites

J'ajouterais aussi que Valgrind-Memcheck est très utile pour détecter les erreurs liées a l'allocation dynamique.

 

Pour l'utiliser : 

valgrind ./votreprogramme

Et avec un makefile c'est encore plus simple  ;)

Share this post


Link to post
Share on other sites

J'ajouterais aussi que Valgrind-Memcheck est très utile pour détecter les erreurs liées a l'allocation dynamique.

 

Pour l'utiliser : 

valgrind ./votreprogramme

Et avec un makefile c'est encore plus simple  ;)

L'utiliser pour les memory leaks et aussi très intéréssente ;).

J'ai déjà cité Valgrind dans le cours #0 justement ;).

 

GDB est aussi parfait pour ce genre de travail.

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
Sign in to follow this  

×
×
  • Create New...