Débuter en langage C - 1

Langage C, premier cours de base

Pas de prise de tête, commencez ici !
Créez un fichier ex1.c contenant le court texte suivant :

main(){}

soit 8 caractères, n’oubliez pas d’aller à la ligne à la fin du texte (toujours une ligne vide à la fin du texte d’un programme) ; compilez-le avec gcc :

gcc ex1.c

exécutez-le, s’il n’y a pas eu d’erreur de compilation :

cromdub@Muir:~$ ./a.out cromdub@Muir:~$
NB :

  • pour exécuter un programme, local à un répertoire, ne pas oublier de le faire précéder de ./ qui signifie « ici ! » ; c’est inutile pour les programmes du système, dont le chemin d’accès est connu, par exemple ‹ gcc ›.
  • le compilateur lui donne le nom implicite a.out puisque nous n’avons rien défini
  • pas d’affichage, notre programme ne faisant rien, mais du coup il le fait très bien et sans erreur possible !
  • sa taille est de 6535 octets.
    Pour un programme « qui ne fait rien », n’est ce pas un peu trop ?
    Le programme a besoin de donner des instructions (et d’en recevoir) au système (noyau Linux), et le noyau au microprocesseur : adresse d’installation en mémoire, allocations de mémoire, appels de librairies statiques ou dynamiques ; puis à la fin il faut des instructions pour laisser la mémoire et le système propres, « comme vous l’avez trouvé en entrant ».

Nommons notre programme explicitement :

gcc -o  ex1  ex1.c

la forme est : gcc -o nom_exécutable nom_programme.c

Ne pourrait-on pas faire « un petit quelque chose » qui se voit à l’écran, au moins ?
Facile, ajoutons un salut, appelons-le ex2.c :

main() { printf("Bonjour le monde !") ; }
On compile :

cromdub@Muir:~$ gcc -o ex2 ex2.c ex2.c: In function "main": ex2.c:4: attention : incompatible implicit declaration of built-in function "printf" cromdub@Muir:~$
Ah, la première erreur de compilation ! Et ce ne sera pas la dernière !
L’instruction ‹ printf › est inconnue. Oui, j’ai fait exprès. Le langage C contient lui-même très peu d’instructions ; ici il ne connaît même pas d’instructions d’entrée - sortie pour les textes. Pour cela il faut lui indiquer dans quelle « librairie » il trouvera printf : c’est dans « stdio.h ». Voici comment le programme s’écrit correctement :

[code]#include <stdio.h>

main()
{
printf(« Bonjour le monde ! ») ;
}[/code]
Compilation et exécution :

cromdub@Muir:~$ gcc -o ex2 ex2.c cromdub@Muir:~$ ./ex2 Bonjour le monde !cromdub@Muir:~$
Ah, et le saut de ligne à la fin du texte ?? Comme ça ce n’est pas très joli.
Rajoutons-le ; il y a deux façons de faire :
1 - à la fin du texte lui-même : printf(« Bonjour le monde !\n ») ;
2 - sur une ligne séparée :
printf(« Bonjour le monde ! ») ;
printf("\n") ;

Compilation, exécution en une seule ligne :

cromdub@Muir:~$ gcc -o ex2 ex2.c && ./ex2 Bonjour le monde ! cromdub@Muir:~$
L’instruction du bash && dit d’exécuter ce qui la suit seulement s’il n’y a pas eu d’erreur avant elle.

Ce premier cours vous montre que ce n’est pas trop difficile ; comme tout, il faut le courage de se lancer et avoir dès le début du succès avec des exercices simples ; puis il faut apprendre et adapter à votre projet. Mais là, comme disait Kipling, « c’est une autre histoire » ! Ou encore : « Pour arriver au but, il faut commencer par faire le premier pas ».

Langage C, cours n° 2

Programme ex3.c : nous pourrons exécuter le programme comme ci-dessus sans argument sur la ligne de commande, ou écrire quelque chose après le nom du programme ; mettez un seul mot, par exemple votre prénom.

[code]/* lecture d’un argument */
#include <stdio.h>

int main(int argc, char **argv)
{
printf("\n") ;

if(argv[1])
printf (« Salut, %s ! », argv[1]) ;
else
printf(« Salut, monde ! ») ;

printf("\n\n") ;
return 0 ;
}[/code]
Compilation, exécution :

[code]cromdub@Muir:~$ gcc -o ex3 ex3.c && ./ex3

Salut, monde !

cromdub@Muir:~$./ex3 Michel

Salut, Michel !

cromdub@Muir:~$[/code]
Et si on veut mettre plus d’un mot ? C’est du bash, non du langage C, il suffit d’enclore le texte entre des guillemets :

[code]cromdub@Muir:~$./ex3 ‹ Michel et Machin ›

Salut, Michel et Machin !

cromdub@Muir:~$[/code]
Remarquez que :

  • tout programme en C commence obligatoirement par une « fonction » appelée main().
  • nous lui passons des arguments : un nombre entier (‹ int ›) argc qui l’informe du nombre des arguments présents sur la ligne de commande (ici deux arguments) ; un tableau de chaînes de caractères (‹ char ›) appelé argv. Les deux astérisques nous indiquent que c’est un pointeur sur des chaînes de caractères… (Voir plus loin les pointeurs).
    Là il faut détailler un peu…
    Il est possible d’écrire de deux façons différentes, le résultat sera le même :
    char ** argv
    char * argv[]

    Je vous renvoie à un cours de C (livre ou Internet, voir « liens »), sur la partie « tableaux et chaînes de caractères ». Ce serait un peu long à détailler pour cette initiation, qui se veut très basique.
  • le premier de ces arguments, qui porte le numéro zéro argv[0] comme toujours en langage C, est le nom du programme ; le second, qui porte le numéro 1, argv[1] est le prénom ou toute autre chaîne de caractères qui suit le nom du programme.
  • if(argv[1]) aurait pu s’écrire : if(argv[1] == !0)
    La première forme, la plus simple, teste implicitement que argv[1] n’est pas zéro, donc est vrai ; la seconde forme le fait explicitement. Bien noter le symbole == qui signifie COMPARER. L’oubli de l’un des signes égal est une faute courante en C, très difficile à détecter. Dans !0 le point d’exclamation inverse la valeur qui le suit, l’expression signifie ici ‹ non zéro ›, ‹ différent de zéro ›, ‹ vrai ›.
    Un programmeur avancé utilisera la première forme, la seconde est plus explicite pour un débutant, sinon plus facile à écrire.
  • cette fonction main() a un paramètre de retour, un entier : int main(). Il permet de renvoyer au bash ou au système qu’il y a eu un problème dans la fonction terminée, c’est le rôle du return 0 ; à la fin de la fonction. Ici sa valeur est zéro, ce qui signfie « pas d’erreur ».
  • une instruction exécutable du langage C se termine par un point-virgule. Bien noter ces deux mots : instruction exécutable ! Faites exprès d’oublier l’un de ces ‹ ; ›, et voyez le compilateur vous le signaler : ligne de l’erreur, n° du caractère dans la ligne.

Programmer c’est aussi apprendre le compilateur.

On voit que nous pénétrons rapidement dans les arcanes du langage C et du compilateur associé gcc, mais rien de vraiment difficile. passons au degré suivant : un appel de fonction.

Langage C, cours n° 3

Appel de fonction : la fonction principale main() ne peut évidemment pas contenir l’ensemble du programme dès qu’il a quelque étendue. Elle fera appel à d’autres fonctions, qui peuvent elles-mêmes appeler des fonctions, etc. Seule main() est obligatoire, comme son nom.

ex4.c : un additionneur simple, dans lequel nous ajoutons le passage par pointeur.
Les pointeurs sont l’un des points très forts du langage C.
Un pointeur est aussi une variable comme les autres, mais sa valeur est une ADRESSE d’une valeur…
Détaillons un peu :
int a = 1 ; ‹ a › est une variable entière (int), la valeur qui lui est affectée par le signe égal est 1.
int *b = &a ; ‹ b › est un pointeur d’entier auquel est affectée l’adresse (instruction &) de ‹ a ›.
*b ou a sont identiques, valant 1 ; on peut utiliser l’un ou l’autre.

[b]Bien distinguer ++++ deux choses DIFFERENTES !!!

  1. la déclaration[/b] d’un pointeur : int *p ; qui signifie ‹ p est un pointeur d’entier › (cela se lit à l’envers)
    2) l’utilisation d’un pointeur : p est un pointeur ; *p est la VALEUR pointée par le pointeur p
    Cette polysémie est un peu gênante, mais si vous avez bien compris ces trois lignes (oui, trois avec le titre), vous avez fait le principal sur les pointeurs, vous apprendrez le reste, certes avec plus ou moins de peine, mais ceci est l’un des « ponts aux ânes » des pointeurs.

[code]/* ex4.c - Exemple de programme utilisant un pointeur.
Voir à : http://c.developpez.com/cours/bernard-cassagne/node46.php
Voir le fonctionnement de scanf dans les docs ou les cours
*/

#include <stdio.h>

/* fonction appelée par la fonction principale main() /
/
c repère l’entier où on veut mettre le résultat, c’est un POINTEUR sur un entier */
void addition(int a, int b, int *c)
{
c = a + b; / « *c » est la VALEUR pointée par « c »… j’insiste +++ ! */
}

int main()
{
int i = 5, j = 2, k = 0 ;

/* on passe les valeurs de i et j comme premiers paramètres /
/
on passe l’ADRESSE de k : « &k » comme troisième paramètre, c’est à dire un pointeur */
addition(i, j, &k) ;

printf("\nLa somme de %d et %d est %d\n\n", i, j, k) ;

return 0 ;
}[/code]
NB : en mettant la fonction main() à la fin du programme, c’est à dire après toutes les autres fonctions appelées, nous nous dispensions de la nécessité d’un fichier d’inclusion (*.h).

Compilons, exécutons :

[code]cromdub@Muir:~$ gcc -o ex4 ex4.c && ./ex4

La somme de 5 et 2 est 7

cromdub@Muir:~$[/code]
Notes :

  • on aurait pu passer les nombres à additionner sur la ligne de commande, mais il aurait fallu transformer les textes ‹ 5 › et ‹ 2 › en nombres 5 et 2 ; faites-le vous-même !
  • ou bien utiliser scanf ; attention, instruction à pièges ; faites-le à titre d’exercice ;
  • pourquoi passer par un pointeur ? Ne peut-on pas additionner dans la fonction secondaire ? Eh bien non ! C’est un problème de pile du microprocesseur, vous verrez ça dans les documentations.

Faire c = a + b dans la fonction ‹ addition › telle qu’elle est ne marche pas comme ça, voila la bonne façon :

int addition(int a, int b) { c = a + b; return c ; }
ou encore, au plus court :

[code]int addition(int a, int b){ return a + b }

int main()
{
int i = 5, j = 2, k = 0 ;

k = addition(i, j) ;
printf("\nLa somme de %d et %d est %d\n\n", i, j, k) ;

return 0 ;
}[/code]
ou aussi :

	printf("\\nLa somme de %d et %d est %d\\n\\n", i, j, addition(i, j)) ;

car addition(i, j) a une valeur : celle donnée par l’instruction ‹ return ›, cette écriture est légitime (et utilisée), mais elle est déconseillée aux tout débutants
Je vous conseille fermement de décomposer vos instructions C, le compilateur vous signalera ainsi aimablement (?) la ligne qui pose problème ; dans les instructions multiples, composées et ramassées, de quelle partie de la ligne peut bien venir l’erreur ??

Et le graphisme, me direz-vous ?
Malheureusement, cette initiation ultrabasique et sans autre prétention est encore bien lointaine de ce qu’il faut apprendre pour simplement ouvrir un vasistas, sans parler d’une fenêtre complexe !
Voici un équivalent graphique du simplissime ex2.c « Bonjour le monde ! » avec du GTK+ :

[code]#include <stdio.h>
#include <gtk/gtk.h>

void FonctionDeRappelBouton(GtkWidget *Bouton)
{
printf(« Bonjour, monde ») ;
}

int main(int argc, char *argv[])
{
GtkWidget *Fenetre;
GtkWidget *Bouton;

/* Initialisation des bibliothèques GTK */
gtk_init(&argc, &argv);

/* Création de l’interface graphique */
Fenetre = gtk_window_new(GTK_WINDOW_TOPLEVEL);
Bouton = gtk_button_new_with_label(« Coucou ! »);
gtk_container_add(GTK_CONTAINER(Fenetre), Bouton);

gtk_widget_show_all(Fenetre);

/* Connexion des signaux */
gtk_signal_connect(GTK_OBJECT(Bouton), « clicked »,
(GtkSignalFunc)FonctionDeRappelBouton, NULL);

/* La boucle principale de gestion des éléments : */
gtk_main();

/* pas de sortie ‹ propre ›, faire un control-C pour terminer */
return 0;
}[/code]
Mais vous en serez bientôt là, n’est-ce pas ??

Notes :

  • c’est le ; qui marque la fin d’une instruction exécutable pour le compilateur, ce qui permet d’écrire une instruction du langage C sur plusieurs lignes pour des facilités de (re)lecture et de compréhension, par exemple :

gdk_draw_rgb_image(Fond, gdk_gc_new(Fenetre), 0,0, LARGEUR, HAUTEUR, GDK_RGB_DITHER_NORMAL, image, 3*LARGEUR) ;

  • ce petit programme n’est pas complet, sa fin n’est pas ‹ propre ›, et il y a d’autres manques.
  • on voit de nombreuses définitions puis utilisations de pointeurs.

Il y a bien d’autres utilisation des pointeurs, puissantes, que vous découvrirez par vous-même. J’espère avoir été bien clair de ne pas confondre la syntaxe de la déclaration et de l’utilisation ! J’insiste encore et à nouveau et toujours. Sans la compréhension complète de ce détail, pas de progression possible.

N’hésitez pas à modifier les programmes proposés et à tester en recompilant, c’est comme ça qu’on apprend.

Langage C, cours n° 4

Introduction à make

Dans les grands ou très grands projets (Linux…), il n’y a pas comme ici un seul fichier, mais des dizaines ou des centaines de fichiers, avec des milliers de fonctions.

Certaines fonctions dépendent d’autres fonctions ou d’autres fichiers. Si l’on modifie une dépendance, il faut recompiler cette fonction, mais aussi celle qui l’appelle, et ainsi de suite en remontant la chaîne des dépendances. Vous voyez le travail ? Insupportable !
Tout ce qui doit être fait deux fois ennuie profondément un vrai programmeur.

Aussi, ‹ make › a été inventé.
(Voir à la fin un exemple simple !)
Voici un exemple pour un programme simple monofichier, multithread, en GTK+ :

[code]NAME = rgb
NAME.C = $(NAME).c
NAME.O = $(NAME).o
CC = gcc
CFLAGS = -Wall -g -ansi
LIBS_GTK = pkg-config --libs gtk+-2.0
FLAGS_GTK = pkg-config --cflags gtk+-2.0
VERSION = 0.99

FLAGS = $(CFLAGS) $(FLAGS_GTK)
LIBS = $(LIBS_GTK)

OBJECTS = $(NAME.O)

$(NAME): $(OBJECTS)
$(CC) $(OBJECTS) $(FLAGS) $(LIBS) -o $(@)

$(NAME.O): $(NAME).c
$(CC) $(FLAGS) -c $(<)

clean:
rm -f *.o *~ $(NAME)[/code]
Le nom recommandé pour ce type de fichier est Makefile (avec une majuscule). Sur la ligne de commande, tapez simplement make ! Il cherche à lire un Makefile dans le répertoire courant, et il s’occupe de tout ! Rien d’autre à mémoriser !
Voyons avec un exemple un peu plus simple de Makefile :

[code]NAME = ex2
NAME.C = $(NAME).c
NAME.O = $(NAME).o
CC = gcc
CFLAGS = -Wall -g -ansi

FLAGS = $(CFLAGS)

OBJECTS = $(NAME.O)

$(NAME): $(OBJECTS)
$(CC) $(OBJECTS) $(FLAGS) -o $(@)

$(NAME.O): $(NAME).c
$(CC) $(FLAGS) -c $(<)

clean:
rm -f *.o *~ $(NAME)[/code]
Les 7 premières lignes déclarent des ‹ macros › internes au Makefile ; certains makefiles peuvent en déclarer des vingtaines. Ensuite, toute partie $( … ) sera remplacée par le contenu de la macro incluse. On voit que les macros peuvent être imbriquées : NAME.C dépend de NAME ; make sait s’en débrouiller.

La forme la plus simplifiée d’un makefile se fait sur deux lignes :
cible:
instruction_exécutable

NB :

  • la cible doit être au bord gauche, pas d’espace avant ; elle doit être terminée par un :
  • instruction_exécutable doit être précédée d’une tabulation.
  • make est d’emploi très général, il ne se limite pas à la compilation ni au langage C ;
  • sa syntaxe spécifique n’est pas toujours très simple à comprendre, sa puissance est considérable
  • il fonctionne au niveau du bash, dont il reprend une partie de la syntaxe.

Exemple simple (enfin!) d’un makefile, qui se contente ici d’afficher un mot en console ; non, celui-ci il ne fait rien d’autre !

[code]un:
@echo un

deux:
@echo deux

trois:
@echo trois

quatre: un trois
@echo quatre

quatre dépend de un et trois[/code]

Utilisation :

cromdub@Muir:~$ make un cromdub@Muir:~$ make un trois un trois cromdub@Muir:~$ make quatre un trois quatre cromdub@Muir:~$
Je ne détaille pas plus avant dans cette initiation de base, mais vous devez absolument en avoir connaissance ; sans l’utilisation de make la compilation de votre programme est une épreuve inutilement pénible.

- tout programme en C commence obligatoirement par une "fonction" appelée [b]main()[/b].
Tout programme en C [b]contient[/b] une fonction main(...) et une seule. Cette fonction est la seule à être [b]déjà définie[/b] et sera la première à être exécuté au lancement du programme.
- nous lui passons des arguments : un nombre entier (int) "argc" qui l'informe de la longueur de la ligne de commande (ici deux arguments) ; un tableau de chaînes de caractères (char).
La fonction [b]main(...) a comme argument[/b] "int argc" et "char *argv" et doit retourner un entier (0 en cas d'exécution correcte du programme). Les noms des variables argc et argv sont utilisés par convention.
- [b]if(argv[1])[/b] aurait pu s'écrire : [b]if(argv[1] == !0)[/b]
ou if (argv[1] != NULL) { ... }

ou

if ( argc == 2) {

}

ou

if (argc > 1) {

}

Je pense qu’il manque la description des types de données supportés par le langage (enum, struct, define, …) ainsi que la déclaration des variables/fonctions avant de parler de graphisme (vaut mieux utiliser libglade) et de makefile. Sinon je conseille le livre suivant pour ceux qui veulent ce mettre au C. Il vous apprendra les bases du langage tout en restant clair et concis (en plus les exemples sont orientés unix).

Titre: Le langage C - norme ANSI
Auteurs: Brian W. Kernighan / Denis M. Ritchie (les concepteurs du langage)
Editeur: masson

Liens

Tout un cours très bien fait… mais d’assez haut niveau.
Shell bash :
http://www-phase.c-strasbourg.fr/inform/linux/cours/user/node12.html#SECTION0012101000000000000000

Langage C, c’est ici :
http://www-phase.c-strasbourg.fr/inform/linux/cours/user/node14.html

Autres liens
Le site « développez.com » semble à connaître, richesse considérable de ressources diverses :
http://c.developpez.com/cours/

Cours de C :
« Introduction au langage C ; Date de publication : 23 Janvier 2007, Par Bernard Cassagne »
http://c.developpez.com/cours/#c
semble très (trop ?) complet ; exemples et exercices.
Exemple tordu :
http://c.developpez.com/cours/bernard-cassagne/node102.php

intérêt des macros en C ; quoi faire et ne pas faire avec des macros :
http://c.developpez.com/cours/bernard-cassagne/node104.php
il y a même un bêtisier du C :
http://c.developpez.com/cours/bernard-cassagne/node166.php
etc.

Autre cours, en pdf, 124 pages. Un peu plus didactique :
ftp://ftp2.developpez.be/developps/c/PolyC.pdf
« Henri Garreta, Université de la Méditerranée Faculté des Sciences de Luminy »
Bien voir le point 8.1-Le préprocesseur, p.103. Déjà signalé par B. Cassagne.

Etc.
A noter : savoir bien programmer en C est un début… Ensuite il faut apprendre à s’en servir !
Voir pourquoi ici par exemple :
http://linux.developpez.com/livres/?page=livresPROG#L2212116012
(livre « Programmation système en C » de C. Blaess).

Le compilateur C « gcc » :
http://c.developpez.com/tutgcc/book1.php
en anglais : http://gcc.gnu.org/

Le programme ‹ time › permet de savoir le temps que prend l’exécution d’un programme et son utilisation du processeur. Il précède l’appel du programme :
time ./mon_prog

Une petite doc sur ‹ make › :
http://www.infres.enst.fr/~vergnaud/documentations/make_notions.html

Et vos propres trouvailles sur Internet, ou par des livres de programmation, d’algorithmique, etc…

J’allais oublier :
Linus Torvalds & David Diamond
Il était une fois Linux

L’extraordinaire histoire d’une révolution accidentelle (c’est le sous-titre !)
éd. Osman Eyrolles multimédia, 2001
Comment peut-on devenir un programmeur acharné, puis faire trembler Microsoft ?

- tout programme en C commence obligatoirement par une "fonction" appelée [b]main()[/b].
Tout programme en C [b]contient[/b] une fonction main(...) et une seule. Cette fonction est la seule à être [b]déjà définie[/b] et sera la première à être exécuté au lancement du programme.
- nous lui passons des arguments : un nombre entier (int) "argc" qui l'informe de la longueur de la ligne de commande (ici deux arguments) ; un tableau de chaînes de caractères (char).
La fonction [b]main(...) a comme argument[/b] "int argc" et "char *argv" et doit retourner un entier (0 en cas d'exécution correcte du programme). Les noms des variables argc et argv sont utilisés par convention.
Titre: Le langage C - norme ANSI Auteurs: Brian W. Kernighan / Denis M. Ritchie (les concepteurs du langage) Editeur: masson
Merci pour les corrections d'erreurs et la citation du livre des origines, mais pas vraiment fait [i]pour des débutants[/i] auxquels ce cours est [i]vraiment[/i] destiné, comme on l'aura compris. C'est pourquoi je n'ai pas parlé des types de données ni du typage fort, ni d'une quantité d'autres bricoles, toutes plus importantes les unes que les autres... A commencer par les fichiers d'inclusions ".h", plutôt que les enum et autres subtilités que l'on trouve lorsqu'on en a enfin besoin, étant déjà bien rodé à ce langage. Je n'ai fait que citer les fichiers multiples sans m'étendre non plus dessus, etc., etc. Ce genre de commentaire de connaisseur risque plus d'éloigner que de rapprocher du début d'une pratique simple, voire simplissime, qui donnera envie de continuer --ou non.

Exemple sur fork()

Voici un exemple beaucoup plus consistant que les très simples programmes proposés jusqu’ici. Nous allons faire du multithread : des parties du programme s’exécutent, plusieurs en même temps… ou presque, à notre échelle.

Ne vous fatiguez pas : faites deux copier-coller :

  • fork.c
  • Makefile

[code]/* « Programmation Linux en langage C »
Warren Gay, Campus Press, octobre 2004 ; Duplication de processus, chapitre 18 page 331
fonction Unix fork()
*/

/* les inclusions /
#include <stdio.h>
#include <stdlib.h> /
appels ‹ system › */
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>

/* quelques définitions de constantes */
#define ERREUR -1
#define ERREUR_WAIT 2
#define SORTIE_ERREUR 13

int main(int argc, char** argv)
{
pid_t pid1, pid2 ;
pid_t wpid1, wpid2 ;
int status ;

/* création de deux processus ‹ enfants › */
pid1 = fork() ;
pid2 = fork() ;

printf("\n\n") ;
if((pid1 == ERREUR) || (pid2 == ERREUR)) /* insuccès, erreur sur fork() /
{
fprintf(stderr, « %s : erreur sur ‹ fork() ›.\n », strerror(errno)) ;
exit(SORTIE_ERREUR) ;
}
/
la variable pid a une autre valeur :/
else if(pid1 == 0) /
processus enfant /
{
printf(« PID 1 %ld : enfant démarré, le parent est %ld.\n »,
(long) getpid(), /
notre pid /
(long) getppid()) ; /
pid du processus enfant */

 printf("PID 2 %ld : enfant démarré, le parent est %ld.\\n",
        (long) getpid(),       /* notre pid               */
        (long) getppid()) ;    /* pid du processus enfant */
 /* Appel au bash,  pour affichage des processus */
 system("ps f") ;

}
else
{
printf(« PID 1 %ld : enfant démarré PID %ld.\n »,
(long) getpid(),
(long) pid1) ;
wpid1 = wait(&status) ;
printf(« PID 1 %ld : enfant démarré PID %ld.\n »,
(long) getpid(),
(long) pid1) ;

 wpid1 = wait(&status) ;
 if(wpid1 == ERREUR)
 {  fprintf(stderr,"%s : erreur sur 'wait()'.\\n", strerror(errno)) ;
    return ERREUR_WAIT ;
 }
 else if(wpid1 != pid1)
    abort() ;
 else
       printf("enfant PID 1 %ld terminé status 0X%04X.\\n",
              (long) pid1, status) ;

 wpid2 = wait(&status) ;
 if(wpid2 == ERREUR)
 {  fprintf(stderr,"%s : erreur sur 'wait()'.\\n", strerror(errno)) ;
    /* return 2 ; */
    exit(ERREUR_WAIT) ;
 }
 else if(wpid2 != pid2)
    abort() ;
 else
    printf("enfant PID 2 %ld terminé status 0X%04X.\\n",
           (long) pid2, status) ;

}

/* sleep(1) ; /
printf("\n") ;
return 0 ;
} /
fin du programme */[/code]
Makefile à utiliser pour la compilation :

[code]NAME = fork
NAME.C = $(NAME).c
NAME.O = $(NAME).o
CFLAGS = -Wall -g -ansi

FLAGS = $(CFLAGS)

OBJECTS = $(NAME.O)

$(NAME): $(OBJECTS)
gcc $(OBJECTS) $(FLAGS) -o $(@)

$(NAME.O): $(NAME).c
gcc $(FLAGS) -c $(<)[/code]
Voilà l’essentiel de ce petit cours d’initiation au langage utilisé par Linus Torvalds (et beaucoup d’autres) pour programmer Linux et la plupart des systèmes d’exploitation et programmes principaux.

Chapitres complémentaires

  • les fichiers d’inclusion dits « .h »
  • historique du langage C
  • etc., etc.

Mais alors on sort d’une initiation, ce qui était mon projet initial.
Si vous n’aviez jamais programmé ou très peu, et que vous êtes arrivés jusqu’ici, voyez avec la bibliographie : livres, Internet et autres ressources.

Programme multi-fichiers

Pour clore ce cours d’initiation, il m’a paru indispensable de donner un exemple de programme comportant plusieurs fichiers. L’élaboration de l’inévitable Makefile n’est pas tout à fait triviale pour un débutant ; de plus il n’existe quasiment pas de programme, sérieux et de quelque étendue, qui soit mono-fichier.

Dans cet exemple qui vise à rester simple, nous voyons l’apparition d’un fichier d’inclusion « .h ».

  • il contient des déclarations (prototypes des fonctions utilisées, constantes, etc.), non de la programmation ;
  • il fait le lien entre les fichiers : en cas d’incohérence entre un ‹ prototype › d’une fonction et son utilisation, il permet au compilateur de vous informer immédiatement de ce genre d’erreur fréquemment commise, surtout dans les gros projets ;
  • il peut exister plusieurs fichiers d’inclusions.

Ne cherchez pas à comprendre la fonctionnement intime du programme, son but est d’abord d’être multi-fichiers, en restant simple : plusieurs fichiers de programmes en C, un fichier d’inclusion « .h », un makefile.
La factorielle d’un nombre (ici 6) qui s’écrit 6! signifie : 6 x 5 x 4 x 3 x 2 x1. On voit vite qu’il suffit de partir de ‹ n ›, de multiplier successivement par ‹ n-1 › jusqu’à 1.

Les fichiers du projet :
recursion.h les déclarations, inclusions, constantes
recursion.c la fonction main() et l’appel des autres fonctions, situées dans des fichiers différents

Chaque fonction appelée :
factorielle() dans f-recurs.c
factoriel_terminal() dans f-term.c
factoriel_iterative() dans f-iter.c

Makefile de type multi-fichiers
NB : make compile l’ensemble des fichiers en un seul : ‹ recursion › ; make clean nettoie le répertoire des fichiers ‹ objets › et autres déchets de la compilation.

Faites des copier-coller !

== Makefile ================================

[code]OBJECTS = recursion.o f-recurs.o f-iter.o f-term.o

all: recursion

CFLAGS = -g -Wall -O2

La règle implicite de compilation

%.o : %.c
gcc -c $(CFLAGS) $< -o $@

La règle explicite d’édition de liens

recursion: $(OBJECTS)
gcc $(OBJECTS) -o recursion -g

clean:
@rm -f *o *.?~

recursion.o: recursion.c recursion.h
f-recurs.o: f-recurs.c recursion.h
f-iter.o: f-iter.c recursion.h
f-term.o: f-term.c recursion.h[/code]
== recursion.h ================================

[code]/* inclusions système */
#include <stdlib.h>

/* déclarations des prototypes des fonctions */
int main (void) ;
unsigned long factorielle(int) ;
unsigned long factoriel_terminal(int, unsigned long) ;
unsigned long factoriel_iterative(unsigned int) ;

/* définition de constantes */
#define EXIT_ERREUR 2[/code]
== recursion.c, le main() ==========================

[code]/* récursivité : http://franckh.developpez.com/tutoriels/c-ansi/recursivite/ */

#include <stdio.h> /* inclusion globale /
#include « recursion.h » /
inclusion locale */

int main(void)
{
unsigned long ret = 0;

/* 1 - Recursivité simple. */
ret = factorielle(6);
printf("\n1 - récursivité simple\n") ;
printf (« 6! = %ld\n », ret);

/* 2 - Recursivite terminale. */
ret = 0;
ret = factoriel_terminal(6, 1);
printf("\n2 - récursivité terminale\n") ;
printf (« 6! = %ld\n », ret);

/* 3 - Fonction iterative. */
ret = 0;
ret = factoriel_iterative(6);
printf("\n3 - itération par boucle for\n") ;
printf (« 6! = %ld\n », ret);

return 0 ;
}[/code]
== f-recurs.c ================================

[code]#include « recursion.h »

/* Fonction recursive simple. */
unsigned long factorielle(int n)
{
if (n < 0)
exit (EXIT_ERREUR);
else if (n == 1 || n == 0)
return 1L;

/* récursion : */
return n * factorielle(n - 1);
}[/code]
== f-iter.c ================================

[code]#include « recursion.h »

/* Fonction iterative. */
unsigned long factoriel_iterative(unsigned int n)
{
unsigned long ret = 1;
unsigned int i = 1;

for (i = 1; i <= n; i++)
ret *= i;

return ret;
}[/code]
== f-term.c ================================

[code]#include « recursion.h »

/* Fonction recursive terminale. */
unsigned long factoriel_terminal(int n, unsigned long result)
{
if (n < 0) exit (EXIT_ERREUR);

if (n == 1) return result;
else if (n == 0) return 1L;

return factoriel_terminal (n - 1, n * result);
}[/code]
== fin ================================

Voici ce cours d’initiation de base au langage C est terminé. A vous de jouer !

Ma doué…

Cours terminé ?
Oui… mais…

Je suis tombé, dans un livre ou plutôt un pavé, sur un problème tordu, lequel m’a immédiatement fait bondir vers mon ordinateur afin de le programmer.
Ne cherchez pas à comprendre le fin mot du problème, ce sont des mathématiques.
En gros : existe-t-il des tests dans des calculs sur des entiers qui ne se terminent pas dans un délai prévisible ?
Des maths de haut niveau, vous dis-je. Mais l’exemple est fort simple, et la programmation ne dépasse pas ce que j’ai déjà montré ci-dessus.

Le programme s’appelle ‹ Syracuse ›, du nom de la ville où la question fut posée.

Il consiste à :
SI le nombre est IMPAIR, il est multiplié par trois et on lui ajoute 1
(vous aurez remarqué qu’il devient pair) ;
SI le nombre est PAIR, il est divisé par deux ;
SI le nombre est égal à 1, fin du calcul.

Il est clair que la programmation sera récursive.
Voilà ce que j’ai commis : syr.c

[code]/* Syracuse :

  • SI le nombre est PAIR, le diviser par deux ;
  • SI le nombre est IMPAIR, le multiplier par trois et lui ajouter 1
    (il devient pair) ;
  • SI le nombre est égal à 1, fin du programme.
    */

#include <stdio.h>
#include <stdlib.h>

/* prototypes des fonctions /
int calculs(int) ;
int main(int, char
*) ;

int main(int argc, char** argv)
{
int nb, ct ;

/* transformer la chaîne de caractères en nombre */
nb = (int) strtol(argv[1], (char **) 0, 10) ;

printf(« nombre racine : %d\n\n », nb) ;
ct = calculs(nb) ;
printf(« nombre d’itérations : %d\n », ct) ;

return 0 ;
}

/* fonction s’appelant récursivment */
int calculs(int calc)
{
int nb ;
static int ct = 1 ;

if (calc % 2 == 0) /* nombre pair ? */
nb = calc / 2 ;
else
nb = calc * 3 + 1 ;

printf("%d\n", nb) ;

/* condition de fin de récursion /
if(nb > 1)
{
ct++ ;
calculs(nb) ; /
appel récursif */
}

return ct ;
}[/code]
et le Makefile correspondant :

[code]NAME = syr
NAME.C = $(NAME).c
NAME.O = $(NAME).o
CC = gcc
CFLAGS = -Wall -g -ansi -D_GNU_SOURCE
VERSION = 1

FLAGS = $(CFLAGS)

OBJECTS = $(NAME.O)

$(NAME): $(OBJECTS)
$(CC) $(OBJECTS) $(FLAGS) -o $(@)

$(NAME.O): $(NAME).c
$(CC) $(FLAGS) -c $(<)

#nettoyage :
clean:
rm -f *.o *~ $(NAME)[/code]
Utilisation - En console (évidemment) vous lancez le programme par :

./syr 15

Vous pouvez mettre un nombre pair, mais vous verrez que cela ne présente aucun intérêt.
Résultat :

[code]cromdub@Muir:~/prog/syrac$ ./syr 17
nombre racine : 17

52
26
13
40
20
10
5
16
8
4
2
1
nombre d’itérations : 12
cromdub@Muir:~/prog/syrac$[/code]
Puissant, non ? Les mathématiciens sont vraiment de très grosses têtes.
Pour nos petits cerveaux, il reste la programmation pour voir ce que donnent ces élucubrations.