« Exercices en langage C/Notions de base » : différence entre les versions

Contenu supprimé Contenu ajouté
Thierry46 (discussion | contributions)
Compléments qualité
Ligne 12 :
 
Écrire un programme qui ne fait rien (''nop'' signifie ''no operation'', soit « aucune opération »).
 
Pour le compiler et l'exécuter :
<pre>
> ccgcc nop.c-Wall -o nop.exe nop.c
> ./nop.exe
>
</pre>
 
'''Remarque qualité''' :
* L'option '''-Wall''' n'est pas obligatoire. Elle demande au compilateur de signaler toutes les fautes qu'il peut détecter. Cela produit quelquefois des ''warnings'' ou avertissement que le programmeur, désireux de produire un code de qualité, s'efforcera de comprendre la signification et de corriger.
* Je donne l'extension .exe à l'exécutable produit pour l'identifier plus facilement. Sous Windows l'extension .exe est obligatoire et rajoutée par l'éditeur de lien si oubli.
 
Notions :
*définition de fonction
*fonction principale <code>main</code> servant de poit d'entrée
*valeur retournée par une fonction
 
Ligne 27 ⟶ 33 :
 
<source lang="C">
/* BonneSolution réponseen retournant 0 */
int main(void)
{
Ligne 34 ⟶ 40 :
</source>
 
Nous pouvons aussi utiliser pour le code retour la constante '''EXIT_SUCCESS''' de ''stdlib.h'' plus parlante que 0.
On trouve des exemples – à commencer par le ''hello world'' de Kernighan et Ritchie – où aucune valeur n'est retournée. Ce n'est pas très dangeureux, mais pas très propre. On trouve aussi des exemples plus anciens (en K&R C, le C des années 1970) où le type <code>int</code> n'est pas déclaré car il était implicite, et où le type <code>void</code> n'existait pas encore. '''Avec un compilateur supportant le K&R C''', on peut donc écrire le programme minimal suivant :
 
<source lang="C">
#include <stdlib.h>
/* Réponse correcte en K&R C */
main(){}
</source>
 
voidint main(void) {}
On trouve enfin fréquemment des exemples où <code>main</code> est défini comme retournant <code>void</code>. Certains compilateurs supportent ce prototype, mais le support du '''prototype <code>void main(void)</code> n'est pas garanti standard''', bien qu'il ne soit pas non plus interdit :
{
 
return EXIT_SUCCESS;
<source lang="C">
}
/* Réponse non portable, valable sur certains compilateurs seulement */
void main(void) {}
</source>
 
}}
 
=== Exercice 2 : programme ''false'' ===
 
Dans les faits, le programme précédent retourne toujours le code de succès 0 ; il correspond donc à la commande Unix ''true''. La commande ''falsetrue'' retourne systématiquement le code d'échecde 1succès 0.
<pre>
MacMini-TM:~ thierry$ true
MacMini-TM:~ thierry$ echo $?
0
</pre>
 
Pour écrire ''faux'' (équivalent du programme UNIX ''false''), cet exercice introduit une complication : l'appel d'une fonction contenue dans le même fichier source que la fonction principale main.
 
PourÉcrire écrirela fonction ''falsemain()'', cet exercice introduit une complication. Écrire d'un programme ''falsefaux'' qui appelle la fonction <code>un</code> suivante et retourne sa valeur à la sortie :
 
La fonction a appeler :
<source lang="c">
int un(void)
Ligne 63 ⟶ 72 :
</source>
 
Pour compiler et exécuter :
<pre>
> ccgcc false.c-Wall -o falsefaux.exe faux.c
> ./faux.exe
> ./false || echo OK
> echo $?
OK
1
>
</pre>
 
Notions :
*déclaration de fonction
*appel de fonction
*prototype de fonction
*appel de fonction
 
{{Boîte déroulante|titre=Solution|contenu =
Ligne 79 ⟶ 89 :
 
<source lang="c">
/* Bonne réponse */
int un(void)
{
Ligne 91 ⟶ 100 :
</source>
 
Remarques qualité :
Que se passe-t-il si l'on définit la fonction <code>un</code> après <code>main</code> ? En compilant <code>main</code>, le compilateur va remarquer que l'on appelle une fonction <code>un</code> dont il ne sait rien. Cela ne l'empêche pas de compiler le programme ! Le compilateur fait comme si <code>un</code> avait été déclarée par : <code>extern int un()</code>. Il y a déclaration implicite de fonction, alors que le compilateur ne sait rien de l'implémentation réelle de la fonction ! Par chance, la déclaration explicite correspond au prototype <code>int un(void)</code> et le programme suivant fonctionne :
* Lorsque on définit une fonction de la sorte, elle est par défaut '''extern''' (visible de l'extérieur). Dans un projet plus important, elle pourrait donc être appelée à la place d'une autre portant le même nom. Pour limiter sa visibilité en interne, il faudait utiliser le mot clé '''static'''. <code>static int un(void)</code>.
 
* Que se passe-t-il si l'on définit la fonction <code>un</code> après <code>main</code> ? En compilant <code>main</code>, le compilateur va remarquer que l'on appelle une fonction <code>un</code> dont il ne sait rien. Cela ne l'empêche pas de compiler le programme ! ('''sans l'option -Wall il ne signalera rien !''') Le compilateur fait comme si <code>un</code> avait été déclarée par : <code>extern int un()</code>. Il y a déclaration implicite de fonction, alors que le compilateur ne sait rien de l'implémentation réelle de la fonction ! Par chance, la déclaration explicite correspond au prototype <code>int un(void)</code> et le programme suivant fonctionne. :Il vaut mieux éviter ces mécanismes hasardeux, en demandant au compilateur d'être strict et en utilisant l'option '''-Wall'''.
<source lang="c">
/* Réponse inélégantedangereuse */
int main(void)
{
Ligne 105 ⟶ 115 :
}
</source>
*Ce qui donnera à la compilation
<pre>
MacMini-TM:~/Documents/developpement/c thierry$ gcc -o faux.exe faux.c
MacMini-TM:~/Documents/developpement/c thierry$ gcc -Wall -o faux.exe faux.c
faux.c: In function 'main':
faux.c:4: warning: implicit declaration of function 'un'
</pre>
 
Si l'on souhaite tout de même mettre la fonction ''main'' en premier, il aurait fallu avertir le compilateur en utilisant un prototype : la signature de la fonction, placé avant le ''main''.
<source lang="Cc">
static int un(void);
 
int main(void)
{
return un();
}
 
static int un(void)
{
return 1;
}
</source>
}}
 
Ligne 112 ⟶ 143 :
 
<pre>
> ccgcc hello.c -o hello.exe hello.c
> ./hello.exe
hello, world
>
Ligne 133 ⟶ 164 :
int main(void)
{
(void)printf("hello, world\n");
return 0;
}
</source>
 
'''Remarques'''
Que se passe-t-il si l'on omet le <code>#include <stdio.h></code> ? Le compilateur va remarquer que l'on appelle une fonction <code>printf</code> dont il ne sait rien. Cela ne l'empêche pas de compiler le programme ! Le compilateur fait comme si <code>printf</code> avait été déclarée par : <code>extern int printf()</code>. Il y a déclaration implicite de fonction, alors que le compilateur ne sait rien de l'implémentation réelle de la fonction ! Si l'on omet d'inclure les prototypes des fonctions appelées, on risque donc d'appeler ces fonctions avec des '''paramètres incompatibles sans avertissement du compilateur''' :
* la fonction printf() retourne le nombre de caractère écrits dans le flux de sortie, ici j'ai décidé de ne pas utiliser ce code retour, je le signale en castant à void la fonction. L'outil qualité lint signale toute fonction retournant une valeur qui n'est pas affectée à une variable ou ignoré explicitement. Cela permet d'éviter l'erreur qui consiste à oublier de traiter un code retour de fonction.
 
* Que se passe-t-il si l'on omet le <code>#include <stdio.h></code> ? Le compilateur va remarquer que l'on appelle une fonction <code>printf</code> dont il ne sait rien. Cela ne l'empêche pas de compiler le programme ! Le compilateur fait comme si <code>printf</code> avait été déclarée par : <code>extern int printf()</code>. Il y a déclaration implicite de fonction, alors que le compilateur ne sait rien de l'implémentation réelle de la fonction ! Si l'on omet d'inclure les prototypes des fonctions appelées, on risque donc d'appeler ces fonctions avec des '''paramètres incompatibles sans avertissement du compilateur''' :
<source lang="C">
/* Appel de printf aberrant risquant de compiler sans avertissement
Ligne 149 ⟶ 181 :
}
</source>
 
L'option de gcc -Wall signale ce problème à la compilation par ce message :
<pre>
hello.c:3: warning: implicit declaration of function 'printf'
</pre>
 
Pour détecter le problème, c'est-à-dire que l'entier <code>123</code> (de type <code>int</code>) est passé au lieu d'une chaîne de caractères (de type <code>const char*</code>), le compilateur doit voir le prototype de <code>printf</code>, qui est : <code>int printf(const char*, ...)</code>. Ce qui nous intéresse avec le <code>#include <stdio.h></code>, c'est que le préprocesseur génère la sortie suivante en entrée du compilateur :
Ligne 168 ⟶ 205 :
</source>
 
Notons que certains compilateurs ont une connaissance implicite des fonctions très communes comme <code>printf</code>. Ainsi, même sans voir le prototype de <code>printf</code>, certaines versions de GCC génèrent un avertissement, à moins d'utiliser la commande de compilation suivante : <code>gcc -fno-builtin -o hello.cexe hello.c</code>.o helloL'option '''-Wall''' active l'option --fno-builtin.
</codepre>.
 
}}
Ligne 174 ⟶ 212 :
=== Exercice 4 : programme comptant les ''arguments'' en ligne de commande'' ===
 
Écrire un programme qui affiche le nombre de paramètres en ligne de commande (en anglais, les ''arguments'' sont les données passées au programme, ou à une fonction).
 
Compilation et exécutions attendues :
<pre>
> ccgcc arg.c-Wall -o arg.exe arg.c
> ./arg.exe
0
> ./arg.exe foo bar
2
>
</pre>
 
Ligne 190 ⟶ 228 :
 
{{Boîte déroulante|titre=Solution|contenu =
Pour avoir accès aux arguments en ligne de commande, on utilise la seconde définition standard de <code>main</code>, qui a pour premier paramètre le nombre d'arguments <code>argc</code>, et pour second paramètre le vecteur d'arguments <code>argv</code>. Notons que sur Unix, le nom de l'exécutable lui-même (<code>./arg</code>) est un argument, donc <code>argc</code> vaut au moins 1. Le résultat recherché est <code>argc - 1</code>. Pour afficher un nombre entier, nous utilisons des caractères de conversion dans la chaîne de formattage passée à <code>printf</code>. Le caractère de conversion <code>id</code> suivant le caractère <code>%</code> indique que nous affichons un entier de type <code>int</code>.
 
<source lang="c">
/* Bonne réponse */
#include <stdio.h>
#include <stdlib.h
 
int main(int argc, char *argv[])
{
(void)printf("%id\n", argc - 1);
return 0EXIT_SUCCESS;
}
</source>