<p>Bonjour à tous,<br/>
Bienvenue dans ce 4ième cours sur la programmation en C.<br/>
Aujourd'hui, nous allons voir un point intéréssent de notre programme, et un point clé en les<br/>
Arguments de lancement, et les allocations dynamiques.</p>
<p><strong>I- Qu'est-ce qu'un argument de lancement?</strong><br/>
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).<br/>
Ainsi, si je lance le programme avec cette ligne de commande:</p>
<blockquote class="ipsQuote" data-ipsquote=""><div>mon_programme mon_argument
<p> </p>
</div></blockquote>
<p>mon programme recevra comme argument 1 : "mon_argument".<br/>
(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).<br/>
Quelques exemples d'utilisations d'arguments:<br/>
Les commandes unix utilisent très souvent les arguments:</p>
<blockquote class="ipsQuote" data-ipsquote=""><div>ls --help<br/>
cd --<br/>
cd /home<br/>
cd ~<br/>
mkdir test<br/>
...
<p> </p>
</div></blockquote>
<p>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:</p>
<p><strong>II- Récupérer des arguments de lancement</strong><br/>
Nous avions vu la structure basique d'un main en C:</p>
<pre class="ipsCode prettyprint">
int main(void)
{
}</pre>
<p>
Eh bien, je ne vous ai pas dit la vérité sur cette fonction!<br/>
En effet, main peut prendre un void pour argument (et donc ne prendre aucun arguments), ou bien prendre un int, et un char **.<br/>
Le véritable prototypage de la fonction main est donc:</p>
<pre class="ipsCode prettyprint">
int main(int argc, char *argv)
{
}</pre>
<p>
En voyant ça, vous allez me dire "dis donc, j'ai mal à la tête, y'a un ** dans ton code!" Eh bien ne<br/>
vous inquiétez pas, il s'agit d'un tableau de chaines de caractères.Prenons pour exemple ce programme lancé comme ceci:</p>
<pre class="ipsCode prettyprint">
coucou arg1 arg2 arg3 je suis fou
</pre>
<p>Eh bien, nous pouvons récupérer les arguments de ce programme de la façon suivante:</p>
<pre class="ipsCode prettyprint">
/
** 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);
}
</pre>
<blockquote class="ipsQuote" data-ipsquote=""><div>"cocuou"<br/>
"arg1"<br/>
"arg2"<br/>
"arg3"<br/>
"je"<br/>
"suis"<br/>
"foud"
<p> </p>
</div></blockquote>
<p>Nous butons à présent sur un problème, ré-utilisons le programme précédent, mais supprimons tous,<br/>
ou un des arguments:</p>
<blockquote class="ipsQuote" data-ipsquote=""><div>Segmentation Fault
<p> </p>
</div></blockquote>
<p>
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.<br/>
Pour remédier à cela, les développeurs nous ont rajoutés "argc".<br/>
Pour mieux vous faire comprendre, je vais vous définir exactement ce que signifie argc et argv:<br/>
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.<br/>
ArgV est un couplage entre les mots: Argument et Value, il s'agit donc des valeurs de ces dits arguments.</p>
<p>
Maintenant que vous avez le nombre d'arguments, vous pouvez vérifier que le nombre d'arguments est correcte avant de l'afficher:</p>
<pre class="ipsCode prettyprint">
int main(int argc, char **argv)
{
if (argc > 1)
my_putstr(argv[1]);
return (0);
}</pre>
<p>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).<br/>
Passons à présent à la partie complexe de ce cours: les allocations dynamiques.</p>
<p><strong>III- Les allocations dynamiques</strong><br/>
L'allocation dynamique, c'est l'art de savoir utiliser sa RAM et les répercussions de l'alloc sur celle-ci.<br/>
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.<br/>
Comme ceci:</p>
<pre class="ipsCode prettyprint">
char mon_tableau[talle_de_mon_tableau];
</pre>
<p>Là est le problème, imaginons que nous devions modifier notre chaine, en lui imposant une taille plus grande: SegFault.<br/>
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))<br/>
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!</p>
<p>
Prenons un exemple simple:<br/>
J'ai un programme qui connaît la taille d'une chaine de caractère, et je dois la stocker dans une variable:</p>
<pre class="ipsCode prettyprint">
int main(void)
{
int lenght = 300000; // Oui, notre chaine est très longue
}</pre>
<p>
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.<br/>
C'est problématique, en effet.<br/>
Tentons donc d’allouer un emplacement dans la ram pour cette chaine:</p>
<pre class="ipsCode prettyprint">
int main(void)
{
char *ptr;
ptr = malloc(300000 * sizeof(char));
}</pre>
<p>
Tiens, ça marche!<br/>
Lisons un peu mieux le man de malloc: en cas d'erreur, malloc retourne NULL.<br/>
Nous allons donc vérifier si il y a eu une erreur (pour éviter le segfault):</p>
<pre class="ipsCode prettyprint">
int main(void)
{
char ptr;
ptr = malloc(300000 * sizeof(char));
if (ptr == NULL)
{
my_putstr("Erreur malloc\n");
return ;
}
}</pre>
<p>
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!<br/>
Nous pouvons donc remplire "ptr" exactement comme un tableau (case par case, voir cours sur les chaines de caractères).<br/>
ATTENTION: ptr est un POINTEUR. Si vous modifiez son adresse, vous perdez l'ancienne adresse allouée! Donc ceci:</p>
<pre class="ipsCode prettyprint">
int main(void)
{
char *ptr;
ptr = malloc(300000 * sizeof(char));
if (ptr == NULL)
{
my_putstr("Erreur malloc\n");
return ;
}
ptr = "coucou";
}
</pre>
<p>est interdit et stupide (ne rigolez pas, les profs de programmation font souvent l'erreur!).<br/>
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!<br/>
Pour la vider, la fonction "free" est là pour nous aider!<br/>
Voilà mon programme une fois la fonction "free" utilisée:</p>
<pre class="ipsCode prettyprint">
int main(void)
{
char *ptr;
ptr = malloc(300000 * sizeof(char));
if (ptr == NULL)
{
my_putstr("Erreur malloc\n");
return ;
}
free(ptr);
}
</pre>
<p>Tout marche! Cool!<br/>
ATTENTION: pour bien vous montrer que modifier un pointeur est une mauvaise idée, tentez de faire ceci:</p>
<pre class="ipsCode prettyprint">
int main(void)
{
char *ptr;
ptr = malloc(300000 * sizeof(char));
if (ptr == NULL)
{
my_putstr("Erreur malloc\n");
return ;
}
ptr = "coucou";
free(ptr);
}</pre>
<p>
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<br/>
allouée.<br/>
Pour vous montrer que mon code fonctionne, je vais afficher ma chaine de caractère, qui fera<br/>
l'alphabet avec 300 000 lettres!</p>
<pre class="ipsCode prettyprint">
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);
}</pre>
<p>
Et voilà, vous savez à présent utiliser les allocations dynamiques!<br/>
A bientôt pour le prochain cours!<br/>
Cours écrit par AlexMog. Contact: alexmog [at] live [point] fr</p>