1 Introduction

1.1 Historique

Le langage de programmation C est la suite naturelle du langage B créé dans la toute fin des années soixante par un grand pionnier de l'informatique moderne: Ken Thompson.

Le langage C a été inventé en 1972 par Brian Kernighan et Dennis Ritchie. Ils sont les concepteurs du système d'exploitation UNIX et ont créé ce nouveau langage pour faciliter leurs travaux de développement logiciel. La saga continue avec Bjarne Stroustrup qui décide d'étendre C en apportant une saveur nouvelle: la programmation orientée objet (OOP), qui fera l'objet d'un cours à part entière. Ce C amélioré voit le jour en 1985.

Il faut attendre 1989 pour que le langage C fasse l'objet d'une normalisation par l'ANSI. L'année suivante le comité ISO ratifie le standard ISO/IEC 9899:1990 communément appelé C90.

Les années se succèdent et le standard évolue pour soit corriger certaines de ses faiblesses soit pour apporter de nouvelles fonctionnalités.

Cinquante ans plus tard, C est toujours l'un des langages de programmation les plus utilisés, car il allie une bonne vision de haut niveau tout en permettant des manipulations de très bas niveau, de fait il est un langage de choix pour les applications embarquées à microcontrôleurs, ou lorsque l'optimisation du code est nécessaire pour obtenir de bonnes performances tels que les noyaux des systèmes d'exploitation comme le noyau Linux (Kernel) ou le noyau Windows.

Il faut retenir que C est un langage simple et efficace. Votre machine à café, votre voiture, vos écouteurs Bluetooth ont très probablement été programmés en C.

../_images/kernighan-ritchie-thompson.jpg

Fig. 1.1 Les pères fondateurs du C

1.2 Standardisation

Vous l'aurez compris à lecture de cette introduction, le langage C possède un grand historique, et il a fallu attendre près de 20 ans après sa création pour voir apparaître la première standardisation internationale.

Le standard le plus couramment utilisé en 2021 est encore C99. C11 le remplace peu à peu dans l'industrie.

Tableau 1.1 Normes internationales du language C

Notation courte

Standard international

Date

C

n/a

1972

K&R C

n/a

1978

C89 (ANSI C)

ANSI X3.159-1989

1989

C90

ISO/IEC 9899:1990

1990

C99

ISO/IEC 9899:1999

1999

C11

ISO/IEC 9899:2011

2011

C17/C18

ISO/IEC 9899:2018

2018

En substance, C18 n'apporte pas de nouvelles fonctionnalités au langage, mais vise à clarifier de nombreuses zones d'ombres laissées par C11.

C11 apporte peu de grands changements fondamentaux pour le développement sur microcontrôleur par rapport à C99 et ce dernier reste de facto le standard qu'il est souhaité de respecter dans l'industrie.

Note

Vous entendrez ou lirez souvent des références à ANSI C ou K&R, préférez plutôt une compatibilité avec C99 au minimum.

Le standard est lourd, difficile à lire et avec 552 pages pour C99, vous n'aurez probablement jamais le moindre plaisir à y plonger les yeux. L'investissement est pourtant parfois nécessaire pour comprendre certaines subtilités du langage qui sont rarement expliquées dans les livres. Pourquoi diable écrire un livre qui détaille l'implémentation C alors qu'il existe déjà ?

Exercice 1.1

Ouvrez le standard C99 et cherchez la valeur maximale possible de la constante ULLONG_MAX. Que vaut-elle ?

1.3 Environnement de développement

Un développeur logiciel passe son temps devant son écran à étudier, et écrire du code et bien qu'il pourrait utiliser un éditeur de texte tel que Microsoft Word ou Notepad, il préfèrera des outils apportant davantage d'interactivité et d'aide au développement. Les smartphones disposent aujourd'hui d'une fonctionnalité de suggestion automatique de mots; les éditeurs de texte orienté programmation disposent de fonctionnalités similaires qui complètent automatiquement le code selon le contexte.

Un autre composant essentiel de l'environnement de développement est le compilateur. Il s'agit généralement d'un ensemble de programmes qui permettent de convertir le code écrit en un programme exécutable. Ce programme peut-être par la suite intégré dans un smartphone, dans un système embarqué sur un satellite, sur des cartes de prototypage comme un Raspberry PI, ou encore sur un ordinateur personnel.

L'ensemble des outils nécessaire à créer un produit logiciel est appelé chaîne de compilation, plus communément appelée toolchain.

Un environnement de développement intégré, ou IDE pour Integrated development environment comporte généralement un éditeur de code ainsi que la toolchain associée.

../_images/ide.svg

Fig. 1.2 Représentation graphique des notions de compilateur, IDE, toolchain...

À titre d'exemple on peut citer quelques outils bien connus des développeurs. Choisissez celui que vous pensez être le plus adapté à vos besoins, consultez l'internet, trouvez votre optimal :

Microsoft Visual Studio

Un IDE très puissant disponible sous Microsoft Windows exclusivement. Il supporte de nombreux langages de programmation comme C, C++, C# ou Python.

Code::Blocks

Un IDE libre et multi-plate-forme pour C et C++, une solution simple pour développer rapidement.

Visual Studio Code

Un éditeur de code open-source multi-plate-forme disponible sur Windows, macOS et Linux. Souvent abrégé VsCode.

GCC

Un compilateur open-source utilisé sous Linux et macOS.

CLANG

Un compilateur open-source gagnant en popularité, une alternative à GCC.

Vim

Un éditeur de code open-source multi-usage à la courbe d'apprentissage très raide et installé par défaut sur la plupart des distributions Unix/Linux. Il est l'évolution de ed, puis ex puis vi puis vim.

Ed

Prononcé "hidi", il s'agit du tout premier éditeur de texte développé en 1969 faisant partie des trois premiers éléments du système UNIX: l'assembleur, l'éditeur et le shell. Il n'est pas interactif, il n'a pas de coloration syntaxique, il est absolument obscur dans son fonctionnement, mais bientôt 50 ans après, il fait toujours partie de la norme POSIX et donc disponible sur tout système compatible. Bref, ne l'utilisez pas...

Exercice 1.2

Un ami vous parle d'un outil utilisé pour le développement logiciel nommé Eclipse. De quel type d'outil s'agit-il ?

1.4 L'Anglais

En programmation, quel que soit le langage utilisé, la langue anglaise est omniprésente. D'une part les mots clés des langages de programmation sont majoritairement empruntés à l'anglais, mais souvent les outils de développement ne sont disponibles qu'en anglais. Il existe une raison à cela. Un article de journal publié dans une revue locale sera certainement lu par madame Machin et monsieur Bidule, mais n'aura aucun intérêt pour les habitants de l'antipode néo-zélandais. En programmation, le code se veut réutilisable pour économiser des coûts de développement. On réutilise ainsi volontiers des algorithmes écrits par un vénérable japonais, ou une bibliothèque de calcul matriciel développée en Amérique du Sud. Pour faciliter la mise en commun de ces différents blocs logiciels et surtout pour que chacun puisse dépanner le code des autres, il est essentiel qu'une langue commune soit choisie et l'anglais est le choix le plus naturel.

Aussi dans cet ouvrage, l'anglais sera privilégié dans les exemples de code et les noms des symboles (variables, constantes ...), les termes techniques seront traduits lorsqu'il existe un consensus établi sinon l'anglicisme sera préféré. Il m'est d'ailleurs difficile, bien que ce cours soit écrit en français de parler de feu d'alerte en lieu et place de warning, car si l'un est la traduction ad hoc de l'autre, la terminologie n'a rien à voir et préfère, au risque d'un affront avec l'Académie, préserver les us et coutumes des développeurs logiciels.

Un autre point méritant d'être mentionné est la constante interaction d'un développeur avec internet pour y piocher des exemples, chercher des conseils, ou de l'aide pour utiliser des outils développés par d'autres. De nombreux sites internet, la vaste majorité en anglais, sont d'une aide précieuse pour le développeur. On peut ainsi citer :

Stack Overflow

Aujourd'hui le plus grand portail de questions/réponses dédié à la programmation logicielle

GitHub

Un portail de partage de code

Google Scholar

Un point d'entrée essentiel pour la recherche d'articles scientifiques

Man Pages

La documentation (man pages) des commandes et outils les plus utilisés dans les environnements macOS/Linux/Unix et POSIX compatible.

Exercice 1.3

Combien y a-t-il eu de questions posées en C sur le site Stack Overflow?

1.5 Apprendre à pêcher

Un jeune homme s'en va à la mer avec son père et lui demande: papa, j'ai faim, comment ramènes-tu du poisson? Le père fier, lance sa ligne à la mer et lui ramène un beau poisson. Plus tard, alors que le jeune homme revient d'une balade sur les estrans, il demande à son père: papa, j'ai faim, me ramènerais-tu du poisson? Le père, sort de son étui sa plus belle canne et l'équipant d'un bel hameçon lance sa ligne à la mer et ramène un gros poisson. Durant longtemps, le jeune homme mange ainsi à sa faim cependant que le père ramène du poisson pour son fils.

Un jour, alors que le fils invective son père l'estomac vide, le père annonce. Fils, il est temps pour toi d'apprendre à pêcher, je peux te montrer encore longtemps comment je ramène du poisson, mais ce ne serait pas t'aider, voici donc cette canne et cet hameçon.

Le jeune homme tente de répéter les gestes de son père, mais il ne parvient pas à ramener le poisson qui le rassasierait. Il demande à son père de l'aide que ce dernier refuse. Fils, c'est par la pratique et avec la faim au ventre que tu parviendras à prendre du poisson, persévère et tu deviendras meilleur pêcheur que moi, la lignée ainsi assurée de toujours manger à sa faim.

La morale de cette histoire est plus que jamais applicable en programmation, confier aux expérimentés l'écriture d'algorithmes compliqués, ou se contenter d'observer les réponses des exercices pour se dire: j'ai compris ce n'est pas si compliqué, est une erreur, car pêcher ou expliquer comment pêcher n'est pas la même chose.

Aussi, cet ouvrage se veut être un guide pour apprendre à apprendre le développement logiciel et non un guide exhaustif du langage, car le standard C99/C11 est disponible sur internet ainsi que le K&R qui reste l'ouvrage de référence pour apprendre C. Il est donc inutile de paraphraser les exemples donnés quand internet apporte toutes les réponses, pour tous les publics du profane réservé au hacker passionné.

1.6 Programmation texte structurée

Le C comme la plupart des langages de programmation utilise du texte structuré, c'est-à-dire que le langage peut être défini par un vocabulaire, une grammaire et se compose d'un alphabet.

À l'inverse des langages naturels comme le Français, un langage de programmation est un langage formel et se veut exact dans sa grammaire et son vocabulaire, il n'y a pas de cas particuliers ni d'ambiguïtés possibles dans l'écriture.

Les compilateurs, sont ainsi construits autour d'une grammaire du langage qui est réduite au minimum par souci d'économie de mémoire, pour taire les ambiguïtés et accroître la productivité du développeur.

L'exemple suivant est un pseudo-code utilisant une grammaire simple :

POUR CHAQUE oeuf DANS le panier :
    jaune, blanc 🠔 CASSER(oeuf)
    omelette 🠔 MELANGER(jaune, blanc)
    omelette_cuite 🠔 CUIRE(omelette)

SERVIR(omelette_cuite)

La structure de la phrase permettant de traiter tous les éléments d'un ensemble d'éléments peut alors s'écrire :

POUR CHAQUE <> DANS <>:
    <>

Où les <> sont des marques substitutives (placeholder) qui seront remplacées par le développeur par ce qui convient.

Les grammaires des langages de programmation sont souvent formalisées à l'aide d'un métalangage, c'est-à-dire un langage qui permet de décrire un langage. La grammaire du langage C utilisé dans ce cours peu ainsi s'exprimer en utilisant la forme Backus-Naur ou BNF disponible en annexe.

1.7 Les paradigmes de programmation

Chaque langage de programmation que ce soit C, C++, Python, ADA, COBOL, Lisp et sont d'une manière générale assez proche les uns des autres. Nous citions par exemple le langage B, précurseur du C (c.f. Section 1.1). Ces deux langages, et bien que leurs syntaxes soient différentes, ils demeurent assez proches, comme l'espagnol et l'italien qui partagent des racines latines. En programmation on dit que ces langages partagent le même paradigme de programmation.

Certains paradigmes sont plus adaptés que d'autres à la résolution de certains problèmes et de nombreux langages de programmation sont dit multi-paradigmes, c'est-à-dire qu'ils supportent différents paradigmes.

Nous citions plus haut le C++ qui permet la programmation orientée objet, laquelle est un paradigme de programmation qui n'existe pas en C.

Ce qu'il est essentiel de retenir c'est qu'un langage de programmation peut aisément être substitué par un autre pour autant qu'ils s'appuient sur les mêmes paradigmes.

Le langage C répond aux paradigmes suivants :

  • Impératif: programmation en séquences de commandes

  • Structuré: programmation impérative avec des structures de contrôle imbriquées

  • Procédural: programmation impérative avec appels de procédures

Le C++ quant à lui apporte les paradigmes suivants à C :

Des langages de plus haut niveau comme Python ou C# apportent davantage de paradigmes comme la programmation réflective.

1.8 Cycle de développement

Le cycle de développement logiciel comprend la suite des étapes menant de l'étude et l'analyse d'un problème jusqu'à la réalisation d'un programme informatique exécutable. Dans l'industrie, il existe de nombreux modèles comme le Cycle en V ou le modèle en cascade. Quel que soit le modèle utilisé, il comprendra les étapes suivantes :

  1. Étude et analyse du problème

  2. Écriture d'un cahier des charges (spécifications)

  3. Écriture de tests à réaliser pour tester le fonctionnement du programme

  4. Conception d'un algorithme

  5. Transcription de cet algorithme en utilisant le langage C

  6. Compilation du code et génération d'un exécutable

  7. Test de fonctionnement

  8. Vérification que le cahier des charges est respecté

  9. Livraison du programme

Mises à part la dernière étape où il n'y a pas de retour en arrière possible, les autres étapes sont itératives. Il est très rare d'écrire un programme juste du premier coup. Durant tout le cycle de développement logiciel, des itérations successives sont faites pour permettre d'optimiser le programme, de résoudre des bogues, d'affiner les spécifications, d'écrire davantage de tests pour renforcer l'assurance d'un bon fonctionnement du programme et éviter une coulée de lave.

Le modèle en cascade suivant résume le cycle de développement d'un programme. Il s'agit d'un modèle simple, mais qu'il faut garder à l'esprit que ce soit pour le développement d'un produit logiciel que durant les travaux pratiques liés à ce cours.

../_images/waterfall.svg

Fig. 1.3 Modèle en cascade

1.9 Cycle de compilation

Le langage C à une particularité que d'autres langages n'ont pas, c'est-à-dire qu'il comporte une double grammaire. Le processus de compilation s'effectue donc en deux passes.

  1. Préprocesseur

  2. Compilation du code

Vient ensuite la phase d'édition des liens ou linkage lors de laquelle l'exécutable binaire est créé.

../_images/build-cycle.svg

Fig. 1.4 Cycle de compilation illustré

Voyons plus en détail chacune de ces étapes.

1.9.1 Préprocesseur (pre-processing)

La phase de preprocessing permet de générer un fichier intermédiaire en langage C dans lequel toutes les instructions nécessaires à la phase suivante sont présentes. Le preprocessing réalise :

  • Le remplacement des définitions par leurs valeurs (#define),

  • Le remplacement des fichiers inclus par leurs contenus (#include),

  • La conservation ou la suppression des zones de compilation conditionnelles (#if/#ifdef/#elif/#else/#endif).

  • La suppression des commentaires (/* ... */, // ...)

Avec gcc il est possible de demander que l'exécution du préprocesseur en utilisant l'option -E.

../_images/preprocessing.svg

Fig. 1.5 Processus de pré-processing

1.9.2 Compilation (build)

La phase de compilation consiste en une analyse syntaxique du fichier à compiler puis en sa traduction en langage assembleur pour le processeur cible. Le fichier généré est un fichier binaire (extension .o ou .obj) qui sera utilisé pour la phase suivante. Lors de la compilation, des erreurs peuvent survenir et empêcher le déroulement complet de la génération de l'exécutable final. Là encore, la correction des erreurs passe toujours par un examen minutieux des messages d'erreur, en commençant toujours par le premier.

Avec gcc il est possible de ne demander que l'assemblage d'un code avec l'option -S.

../_images/assembly.svg

Fig. 1.6 Assemblage d'un programme C pré-processé en assembleur

../_images/build.svg

Fig. 1.7 Traduction d'un programme C pré-processé en objet binaire

1.10 Une affaire de consensus

En informatique comme dans la société humaine, il y a les religieux, les prosélytes, les dogmatiques, les fanatiques, les contestataires et les maximalistes. Le plus souvent les motifs de fâcheries concernent les outils que ces derniers utilisent et ceux dont on doit taire le nom. Ils se portent parfois sur les conventions de codage à respecter, l'encodage des fichiers, le choix de l'EOL, l'interdiction du goto, le respect inconditionnel des règles MISRA. Il existe ainsi de longues guerres de croyances, parfois vielles de plusieurs générations et qui perdurent souvent par manque d'ouverture d'esprit et surtout parce que la bonne attitude à adopter n'est pas enseignée dans les écoles supérieures là où les dogmes s'établissent et pénètrent les esprits dociles, faute au biais d'ancrage mental. L'enseignant devrait être sensible à ces aspects fondamentaux et devrait viser l'impartialité en visant l'ouverture l'esprit et le culte du bon sens de l'ingénieur.

Citons par exemple les guerres d'éditeurs qui date des années 1970 et qui opposent les défenseurs de l'éditeur vi aux inconditionnels d'emacs. Il s'agit de deux éditeurs de texte très puissants et à la courbe d'apprentissage raide qui séparent les opinions tant leur paradigme de fonctionnement est aporétique. Ces guerres sont d'abord entretenues par plaisir de l'amusement, mais les foules de convertis ne s'aperçoivent pas toujours de l'envergure émotionnelle que prend l'affaire dans son ensemble et force est de constater qu'avec le temps ils ne parviennent plus à percevoir le monde tel qu'il est, à force d'habitudes.

S'enterrer dans une zone de confort renforce le biais du Marteau de Maslow, car lorsque l'on est un marteau, on ne voit plus les problèmes qu'en forme de clou. Cette zone de confort devient un ennemi et barre l'accès au regard critique et au pragmatisme qui devrait prévaloir. Car accepter l'existence de différentes approches possibles d'un problème donné est essentiel, car plus que dans tout autre domaine technique, le développement logiciel est avant tout une aventure collaborative qui ne devrait jamais être sous le joug d'une quelconque emprise émotionnelle.

Un programme se doit d'être le plus neutre possible, impartial et minimaliste. Il n'est pas important de se préoccuper des affaires cosmétiques telles que la position des accolades dans un programme, le choix d'utiliser des espaces versus des tabulations horizontales, ou le besoin d'utiliser tel ou tel outil de développement parce qu'il est jugé meilleur qu'un autre.

La clé de la bonne attitude c'est d'être à l'écoute du consensus de ne pas sombrer au biais d'attention. Il faut non seulement être sensible au consensus local direct: son entreprise, son école, son équipe de travail, mais surtout au consensus planétaire dont l'accès ne peut se faire que par l'interaction directe avec la communauté de développeurs, soit par les forums de discussions (Reddit, stackoverflow), soit par le code lui-même. Vous avez un doute sur la bonne méthode pour écrire tel algorithme ou sur la façon dont votre programme devrait être structuré ? Plongez-vous dans le code des autres, multipliez vos expériences, observez les disparités et les oppositions, et apprenez à ne pas y être sensible.

Vous verrez qu'au début, un programme ne vous semble lisible que s'il respecte vos habitudes, la taille de vos indentations préférées, la police de caractère qui vous sied le mieux, l'éditeur qui supporte les ligatures, car admettez-le est infiniment plus lisible que fi. Par la suite, et à la relecture de cette section, vous apprendrez à faire fi de cette zone de confort qui vous était si chère et que l'important n'est plus la forme, mais le fond. Vous aurez comme Néo, libéré votre esprit et serez capable de voir la matrice sans filtre, sans biais.

En somme, restez ouvert aux autres points de vues, cherchez à adopter le consensus majoritaire qui dynamise au mieux votre équipe de développement, qui s'encadre le mieux dans votre stratégie de croissance et de collaboration et surtout, abreuvez-vous de code, faites-en des indigestions, rêvez-en la nuit. Vous tradez du Bitcoin, allez lire le code source, vous aimez Linux, plongez-vous dans le code source du kernel, certains collègues ou amis vous ont parlé de Git, allez voir ses entrailles... Oui, tous ces projets sont écrits en C, n'est-ce pas merveilleux ?

1.11 Hello World!

Il est traditionnellement coutume depuis la publication en 1978 du livre The C Programming Language de reprendre l'exemple de Brian Kernighan comme premier programme.

#include <stdio.h>

int main(void)
{
    printf("hello, world\n");
    return 0;
}

Ce programme est composé de deux parties. L'inclusion de la library standard d'entrées sorties (STandarD Inputs Outputs) qui définit la fonction printf, et le programme principal nommé main. Tout ce qui se situe à l'intérieur des accolades { } appartient au programme main.

L'ensemble que définit main et ses accolades est appelé une fonction, et la tâche de cette fonction est ici d'appeler une autre fonction printf dont le nom vient de print formatted.

L'appel de printf prend en paramètre le texte Hello world!\n dont le \n représente un retour à la ligne.

Une fois le code écrit, il faut le compiler. Pour bien comprendre ce que l'on fait, utilisons la ligne de commande ; plus tard, l'IDE se chargera de l'opération automatiquement.

Une console lancée ressemble à ceci, c'est intimidant si l'on n’en a pas l'habitude, mais vraiment puissant.

$

La première étape est de s'assurer que le fichier test.c contient bien notre programme. Pour ce faire on utilise un autre programme cat qui ne fait rien d'autre que lire le fichier passé en argument et de l'afficher sur la console :

$ cat hello.c
#include <stdio.h>

int main(void)
{
    printf("hello, world\n");
    return 0;
}

À présent on peut utiliser notre compilateur par défaut: cc pour C Compiler. Ce compilateur prends en argument un fichier C et sans autre option, il génèrera un fichier a.out pour assembler output. C'est un fichier exécutable que l'on peut donc exécuter.

$ gcc hello.c

Il ne s'est rien passé, c'est une bonne nouvelle. La philosophie Unix est qu'un programme soit le plus discret possible, comme tout s'est bien passé, inutile d'informer l'utilisateur.

On s'attend donc à trouver dans le répertoire courant, notre fichier source ainsi que le résultat de la compilation. Utilisons le programme ls pour le vérifier

$ ls
hello.c       a.out

Très bien ! À présent, exécutons le programme en prenant soin de préfixer le nom par ./ car étant un programme local a.out ne peut pas être accédé directement. Imaginons qu'un fourbe hacker ait décidé de créer dans ce répertoire un programme nommé ls qui efface toutes vos données. La ligne de commande ci-dessus aurait eu un effet désastreux. Pour remédier à ce problème de sécurité, tout programme local doit être explicitement nommé.

$ ./a.out
hello, world

Félicitations, le programme s'est exécuté.

Pouvons nous en savoir plus sur ce programme ? On pourrait s'intéresser à la date de création de ce programme ainsi qu'à sa taille sur le disque. Une fois de plus ls nous sera utile, mais cette fois-ci avec l'option l:

$ ls -l a.out
-rwxr-xr-- 1 ycr iai 8.2K Jul 24 09:50 a.out*

Décortiquons tout cela :

-             Il s'agit d'un fichier
rwx           Lisible (r), Éditable (w) et Exécutable (x) par le propriétaire
r-x           Lisible (r) et Exécutable (x) par le groupe
r--           Lisible (r) par les autres utilisateurs
1             Nombre de liens matériels pour ce fichier
ycr           Nom du propriétaire
iai           Nom du groupe
8.2K          Taille du fichier, soit 8200 bytes soit 65'600 bits
Jul 24 09:50  Date de création du fichier
a.out         Nom du fichier

Exercice 1.4

Qui a inventé le C ?

Exercice 1.5

Quel est le standard C à utiliser en 2021 et pourquoi ?

Exercice 1.6

Quels sont les paradigmes de programmation supportés par C ?

Exercice 1.7

Pourriez-vous définir ce qu'est la programmation impérative ?

Exercice 1.8

Qu'est-ce qu'une coulée de lave en informatique ?

Exercice 1.9

Qu'est-ce que cat?