le langage C

This commit is contained in:
tth 2021-08-23 09:54:19 +02:00
parent 323fc07633
commit 459cf8a8c8
1 changed files with 309 additions and 9 deletions

View File

@ -1,10 +1,16 @@
\chapter{Langage C}
\chapter{Le langage C}
\label{C}\index{C}
Un chapitre un peu particulier, puisque c'est le début d'une
initiation au langage C pour les non-codeurs qui souhaitent
rentrer dans le sujet à la dure.
Certaines des explications qui vont suivre ne sont pas très
rigoureuses, mais montrent bien les principes généraux et
les erreurs classiques. La futilité des exemples est assumée.
Les détails nécessaires seront présentés dans la suite.
% ---------------------------------------------------------
\section{Hello World}
@ -15,10 +21,14 @@ Le tout début d'un programme en C est l'appel par
le \textsl{runtime} d'une fonction
nommée \texttt{main} qui reçoit deux paramètres
dont l'utilisation est décrite un peu plus bas.
Ces paramètres sont fournis par le système d'exploitation.
\lstinputlisting[language=c]{code/hello.c}
Un fois passé l'entrée, nous somme dans la partie active.
Nous appelons à ce moment la fonction \texttt{printf}
qui a pour but d'afficher sur l'écran le texte
passé en paramètre.
Voila, c'est fait. Nous savons dire bonjour au monde.
Point suivant~: dire boujour à quelqu'un d'autre. Et pour
@ -30,27 +40,317 @@ de commande.
\section{Arguments}
Expliquons maintenant les deux paramètres
\texttt{argc} et \texttt{argv}.
\texttt{argc} et \texttt{argv} du point d'entrée du programme
(la fonction main).
Le premier est le nombre de "mots" détectés par l'interpréteur
de commande qui va lancer votre proggy, et le second est
un tableau contenant ces différents mots.
un tableau de chaines de caractères contenant ces différents mots.
Ce petit bout de code va nous afficher tout ça~:
\lstinputlisting[language=c]{code/arguments.c}
Et voici un exemple d'exécution depuis un shell~:\index{shell}
\begin{verbatim}
$ ./arguments un deux "trois quatre"
0 ./arguments.
1 un.
2 deux.
3 trois quatre.
0 ./arguments
1 un
2 deux
3 trois quatre
$
\end{verbatim}
Nous constatons que la première valeur affichée est en fait
le nom de notre programme, ou plutôt le chemin vers le fichier
exécutable, et surtout que son indice est 0, ce qui semble
logique\footnote{Rez-de-chaussée, premier étage, toussa\dots}.
En C, les tableaux commencent toujours à l'indice 0.
Pour le traitement des options, il faut sauter à
la page \pageref{getopt}.
% ---------------------------------------------------------
\section{Les variables}
En gros, une variable du C est une zone de mémoire destinée
à contenir une valeur.
Une variable peut être caractérisée par trois choses~:
son nom, son type, et sa portée.
\textbf{Le nom} : Il doit commencer par une
lettre\footnote{To be ASCII or not to be ?} majuscule ou minuscule,
laquelle peut être suivie d'un nombre suffisant de lettres, de chiffres
et du caractère 'souligné'. La différence de casse est signifiante.
\textbf{Le type} : C'est une désignation du genre d'information
que l'on peut stocker dans une variable.
\textbf{La portée} :
% ---------------------------------------------------------
\section{Les fonctions}
Nous avons vu brièvement dans la première section de ce chapitre
la fonction \texttt{main} et ses deux paramètres.
Il est temps de préciser les détails.
% ---------------------------------------------------------
\section{Entrées / Sorties}
\index{stdin} \index{stdout} \index{stderr}
\subsection{Les trois flux canoniques}
\texttt{stdin}, \texttt{stdout} et \texttt{stderr}\dots
Par défaut, au lancement du programme, ces trois canaux
d'entrée/sortie sont pré-connectés, et donc directement
utilisables.
Le premier (\texttt{stdin}), l'entrée standard, est connecté
au clavier du consoliste\footnote{De quel roman est tiré ce terme ?},
le second (\texttt{stdout}), la sortie standard, permet d'afficher
les résultats du programme sur l'écran,
et le troisème (), la sortie d'erreur, permet d'afficher
les éventuels messages d'erreur.
La différence entre \textsl{out} et \textsl{err} est expliquée
plus loin.
\subsection{IO de base}\index{getchar}\index{putchar}
Les fonctions \texttt{getchar} et \texttt{putchar} sont les plus
simples.
Avec elles, on peut lire un caractère depuis \texttt{stdin},
et afficher un caractère dans \texttt{stdout}. Tout cela semble
très bien, mais la fonction de lecture est piégeuse
"\textsc{It's a trap}", comme nous allons bientôt le voir.
\subsection{Écrire : \texttt{printf}}
\index{printf}
La fonction \texttt{printf} permet d'afficher le contenu d'une
(ou plusieurs)
variable sous divers formats contrôlables par un petit DSL.
\begin{verbatim}
int bar, foo = 42;
bar = printf ("foo = %d\n", foo);
\end{verbatim}
La première ligne déclare deux variables dont une (foo) est
initialisée à une valeur connue. La seconde ligne appelle
la fonction '\texttt{printf}' avec les paramêtres appropriés et
conserve la valeur de retour de celle-ci.
Le premier de ces paramêtres, \texttt{"foo = \%d\textbackslash{}n"},
est appelé \emph{chaine de format}. C'est une chaine de caractères
que l'on peut classer en trois catégories~:
\textsl{caractères crus} : ils sont directements poussés vers la sortie.
\textsl{codes de format} : les fragments qui commencent par le
caractère '\%'.
\textsl{échappements} : les caratères précédés d'un
'\textbackslash'.
\subsection{Lire : \texttt{scanf}}
\index{scanf}
Avez-vous bien révisé la section qui cause des pointeurs ?
% ---------------------------------------------------------
\section{Un filtre Unix}\label{filtre-unix}
Ce qu'on appelle un "filtre" est un logiciel destiné à transformer
les données qui le traversent. Pour bien comprendre, le mieux,
comme d'habitude, est un exemple. Il est un peu artificiel,
il s'agit d'éliminer les lettres \textsc{o} et \textsc{p}.
Le voici~:
\lstinputlisting[language=c]{code/no-op.c}
Démonstration~:
\begin{verbatim}
$ echo apopoz | ./no-op
az
$ ./no-op < no-op.c | head -5
/*
* n-.c is an useless shell filter
*/
#include <stdi.h>
#include <ctye.h>
$
\end{verbatim}
Pour une explication plus détaillée sur les mécanismes
utilisés et les usages possibles,
il faut voir le concept de pipeline en
page \pageref{pipeline}.
% ---------------------------------------------------------
\section{Les pointeurs}\label{pointeur}\index{pointeur}
\textbf{Ah, enfin, on a failli attendre !}
Qu'est-ce qu'un pointeur ? La réponse est multiple, et c'est
le pointeur qui fait à la fois la force et la faiblesse du C.
Pour faire simple, un pointeur est une variable qui contient
l'adresse dans la mémoire d'une autre variable.
% ---------------------------------------------------------
\section{Le préprocesseur}\index{cpp}
Nous avons déja abordé de loin la directive \texttt{\#include},
qui fait partie du préprocesseur, comme toutes les lignes
de code commençant par le caractère \textbf{\#}.
Le concept de base, qu'il faut bien capter, est que le
préprocesseur pratique des \emph{substitutions de texte}.
Pratiquement, il se passe ça~:
\begin{verbatim}
#define NUMID 1664
#define FLAVOUR "tisane"
printf("La %d c'est de la %s\n", NUMID, FLAVOUR);
\end{verbatim}
Ces trois lignes de code
(deux directives pour cpp et un appel classique de fonction)
seront converties en une seule ligne de C.
\begin{verbatim}
printf("La %d c'est de la %s\n", 1664, "tisane");
\end{verbatim}
C'est donc cette ligne qui sera ensuite passée au vrai compilateur
pour être traduite en codes opératoires, dont l'exécution
affichera une maxime dont la véracité demande à être vérifiée
par l'expérimentation. Mais ça n'est pas fini.
Ce sympathique \textsf{préproc'} nous permet aussi de faire
de la compilation conditinnelle, et ça, c'est cool parce que
ça ouvre la porte à plein de choses.
Par exemple, l'instrumentation du code afin de faciliter
les tests et le debug.
\begin{verbatim}
...
#ifdef TRACEUR
fprintf(stderr, "pid %d was here.\n", getpid());
#endif
...
\end{verbatim}
Et à la compilation, il vous faut passer l'option
\texttt{-DTRACEUR} à Gcc pour que ce message de trace soit
pris en compte.
% ---------------------------------------------------------
\section{Unités de compilation}
Jusque à maintenant, nous n'avons vu que des programmes dont
le code source n'était que dans un seul fichier, ce qui devient
vite ingérable pour un gros projet. C permet facilement
de faire de la compilation séparée~: chacun des fichiers source
est compilé indépendament en un fichier \textsl{objet}, lesquels
seront ensuite \textbf{liés} pour obtenir l'exécutable final.
Mais découper un gros logiciel en plusieurs fichiers source
a d'autres avantages.
% ---------------------------------------------------------
\section{Les structures}
Une structure est une sorte de boite dans laquelle on peut
ranger plusieurs variables afin de les manipuler comme
une seule entité.
% ---------------------------------------------------------
\section{Gestion de la mémoire}
\index{malloc} \index{free}
Nous avons déja entrevu la gestion « implicite » de la mémoire
avec les variables locales. Il est temps de passer à une gestion
explicite de celle-ci~:
les fonctions \texttt{malloc} et \texttt{free} sont là pour ça.
La première demande au mc/p de nous préter une certaine quantité
de mémoire, que nous pourront utiliser à notre guise.
Et la seconde restitue la zone mémoire au système sous-jacent.
Un rapide synopsis minimal d'utilisation~:
\begin{verbatim}
int foo, *ptr;
if (NULL==(ptr=malloc(sizeof(int)*NBITEMS))) abort();
for (foo=0; foo<NBITEMS; foo++) ptr[foo] = rand();
do_something(ptr);
free(ptr);
\end{verbatim}
Pour les besoins de la démo, nous avons deux variables, l'une
est entière (\texttt{foo}) et l'autre, \texttt{ptr}, est
un pointeur sur $N$ entiers.
Ce pointeur est initialisé sur la seconde ligne par un appel
à la fonction \texttt{malloc(3)} avec en paramètre le
nombre d'\textbf{octets} que nous voulons emprunter.
Ce nombre est ici calculé en multipliant le nombre de case
désiré par la taille de la case, une bonne occasion de
découvrir l'opérateur \texttt{sizeof}\dots
La boucle \texttt{for} de la ligne suivante insère des données
pertinentes dans notre tableau. Lesquelles données seront
habilement traitées sur la ligne suivante par l'appel
de cette fonction~:
\begin{verbatim}
void do_something(int values[])
{
int foo;
double sum = 0.0;
for (foo=0; foo<NBITEMS; foo++) sum += (double)values[foo];
printf("sum is %g\n", sum);
}
\end{verbatim}
Et finalement, nous avons réussi à générer un \textsl{useless number}.
Notre mission est terminée, nous rendons notre bloc de mémoire
au mc/p avec \texttt{free(ptr);}.
% ---------------------------------------------------------
\section{Gérer les options}\index{getopt}\label{getopt}
\begin{verbatim}
#include <unistd.h>
int getopt(int argc, char * const argv[], const char *optstring);
extern char *optarg;
extern int optind, opterr, optopt;
\end{verbatim}
La page de man de getopt(3) contient des explications détaillées
et un exemple simple d'utilisation.
% =========================================================
\section{Ailleurs dans cet ouvrage}
Il y a plein d'autres exemples de code en C, sur des sujets
divers comme Open Sound Control (page \pageref{chap:OSC}) ou
libsndfile (page \pageref{chap:son}).
Et pour les gens du système~:
l'utilisation des signaux (page \pageref{get-signal}).
% ---------------------------------------------------------