Exercices en langage C/Notions de base
Ces exercices sur les notions de base abordent :
- définition de fonction
- la fonction
main
- l'appel de fonction
- la bibliothèques standard, l'inclusion avec le préprocesseur
- la sortie formatée avec
printf
- les paramètres de
main
Exercice 1 : programme minimal nop
[modifier | modifier le wikicode]Écrire un programme qui ne fait rien (nop signifie no operation, soit « aucune opération »).
Pour le compiler et l'exécuter :
> gcc -Wall -o nop.exe nop.c > ./nop.exe >
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
main
servant de point d'entrée - valeur retournée par une fonction
Un programme C commence par l'exécution d'une fonction main
. Il existe deux prototypes standards pour la fonction main
: int main(void)
et int main(int argc, char* argv[])
. Nous utilisons le plus simple car nous n'avons pas besoin de argc
ni argv
. Remarquons que les deux prototypes indiquent que main
retourne un entier de type int
. Nous retournons 0 (zéro), valeur conventionnelle pour indiquer que le programme est terminé sans erreur. Voici notre définition de la fonction main
:
/* Solution en retournant 0 */
int main(void)
{
return 0;
}
Nous pouvons aussi utiliser pour le code retour la constante EXIT_SUCCESS de stdlib.h plus parlante que 0.
#include <stdlib.h>
int main(void)
{
return EXIT_SUCCESS;
}
Exercice 2 : programme false
[modifier | modifier le wikicode]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 true retourne systématiquement le code de succès 0.
MacMini-TM:~ thierry$ true MacMini-TM:~ thierry$ echo $? 0
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.
Écrire la fonction main() d'un programme faux qui appelle la fonction un
suivante et retourne sa valeur à la sortie :
La fonction a appeler :
int un(void)
{
return 1;
}
Pour compiler et exécuter :
> gcc -Wall -o faux.exe faux.c > ./faux.exe > echo $? 1
Notions :
- déclaration de fonction
- appel de fonction
- prototype de fonction
On définit la fonction un
avant la fonction main
. Dans main
, à l'aide de l'opérateur d'appel de fonction ()
, on procède à l'appel de fonction un()
, dont on retourne immédiatement la valeur.
int un(void)
{
return 1;
}
int main(void)
{
return un();
}
Remarques qualité :
- 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 faudrait utiliser le mot clé static.
static int un(void)
. - Que se passe-t-il si l'on définit la fonction
un
aprèsmain
? En compilantmain
, le compilateur va remarquer que l'on appelle une fonctionun
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 siun
avait été déclarée par :extern int un()
. 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 prototypeint un(void)
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.
/* Réponse dangereuse */
int main(void)
{
return un();
}
int un(void)
{
return 1;
}
- Ce qui donnera à la compilation
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'
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.
static int un(void);
int main(void)
{
return un();
}
static int un(void)
{
return 1;
}
Exercice 3 : programme hello, world
[modifier | modifier le wikicode]Écrire un programme qui affiche hello, world.
> gcc -o hello.exe hello.c > ./hello.exe hello, world >
Notions :
- inclusion d'en-tête standard
- passage de paramètres de fonction
printf
- chaîne de caractères statique
On utilise le préprocesseur pour inclure stdio.h
car le prototype de printf
se trouve dans stdio.h
. On passe la chaîne de caractères "hello, world\n"
à l'appel de printf
. On remarque que la chaîne est terminée par un caractère de nouvelle ligne (\n
). Sans cela, une fois le programme terminé, l'invite de commande risque d'être affichée à la suite du mot world au lieu de se trouver au début d'une nouvelle ligne.
/* Bonne réponse */
#include <stdio.h>
int main(void)
{
(void)printf("hello, world\n");
return 0;
}
Remarques
- 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
#include <stdio.h>
? Le compilateur va remarquer que l'on appelle une fonctionprintf
dont il ne sait rien. Cela ne l'empêche pas de compiler le programme ! Le compilateur fait comme siprintf
avait été déclarée par :extern int printf()
. 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 :
/* Appel de printf aberrant risquant de compiler sans avertissement
faute d'avoir inclu stdio.h. Un crash à l'exécution est probable. */
int main(void)
{
printf(123);
return 0;
}
L'option de gcc -Wall signale ce problème à la compilation par ce message :
hello.c:3: warning: implicit declaration of function 'printf'
Pour détecter le problème, c'est-à-dire que l'entier 123
(de type int
) est passé au lieu d'une chaîne de caractères (de type const char*
), le compilateur doit voir le prototype de printf
, qui est : int printf(const char*, ...)
. Ce qui nous intéresse avec le #include <stdio.h>
, c'est que le préprocesseur génère la sortie suivante en entrée du compilateur :
/* Ce que le compilateur voit */
/* Contenu de stdio.h... */
/* Prototype de printf */
int printf(const char *, ...);
/* ... fin de stdio.h */
int main(void)
{
printf("hello, world\n");
return 0;
}
Notons que certains compilateurs ont une connaissance implicite des fonctions très communes comme printf
. Ainsi, même sans voir le prototype de printf
, certaines versions de GCC génèrent un avertissement, à moins d'utiliser la commande de compilation suivante : gcc -fno-builtin -o hello.exe hello.c
. L'option -Wall active l'option --fno-builtin.
Exercice 4 : programme comptant les arguments en ligne de commande
[modifier | modifier le wikicode]É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 :
> gcc -Wall -o arg.exe arg.c > ./arg.exe 0 > ./arg.exe foo bar 2
Notions :
- paramètres de
main
- sortie formatée
Pour avoir accès aux arguments en ligne de commande, on utilise la seconde définition standard de main
, qui a pour premier paramètre le nombre d'arguments argc
, et pour second paramètre le vecteur d'arguments argv
. Notons que sur Unix, le nom de l'exécutable lui-même (./arg
) est un argument, donc argc
vaut au moins 1. Le résultat recherché est argc - 1
. Pour afficher un nombre entier, nous utilisons des caractères de conversion dans la chaîne de formatage passée à printf
. Le caractère de conversion d
suivant le caractère %
indique que nous affichons un entier de type int
.
/* Bonne réponse */
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
(void)printf("%d\n", argc - 1);
return EXIT_SUCCESS;
}