Le code source d'un programme possède certains éléments distinctifs propres au langage de programmation employé :

  • Principalement, c'est la syntaxe du code qui caractérise le langage, c'est‑à‑dire l'ensemble des règles formelles d'écriture du code. Si des règles de syntaxe peuvent être communs à plusieurs langages, il a toujours des aspects spécifiques propres à chacun.
  • Accessoirement, ce sont aussi les extensions des noms des fichiers dans lesquels le code source est écrit W qui signent extérieurement le langage utilisé. Ces extensions sont essentielles pour les éditeurs de code et les outils de traduction du code source en langage machine (typiquement, la chaîne de compilation).

Pour un codeur débutant, parmi les aspects du langage qui doivent d'abord attirer l'attention lieu figure la structure générale du programme, lequel n'est jamais purement séquentiel. Et dans cette structure, il importe de savoir immédiatement repérer ce qu'on appelle le point d'entrée, c'est‑à‑dire l'élément de code où débute l'exécution, qui n'est jamais la première ligne du fichier.

L'objectif de ce court chapitre est d'apporter toutes les connaissances pour pouvoir identifier les éléments qui forment le squelette minimal, commun à tous programmes écrits dans un même langage, C ou C++, et dans le framework Arduino.

Également, on aborde déjà la notion de fonction, qui joue un rôle essentiel dans la structure des programmes, puisqu'elle recouvre ce que l'on appelle communément un sous‑programme. Cette notion est incontournable, notamment pour comprendre comment fonctionne Arduino IDE pour compiler en C++ un programme sans fonction main apparente.

La notion de fonction sera revue en détail au chapitre C4‑I  et plus généralement dans toutes la partie C4 du module.

Squelette d'un programme en langages C et C++

En langage C

La fonction principale main et son fichier source

Dans un environnement d'exécution hébergé par un système d'exploitation (hosted environment – cf. chap. C2‑II ) – typiquement, un poste de travail – l'exécution d'un programme en langage C commence nécessairement par l'appel du code exécutable d'une fonction principale main, laquelle constitue ce qu'on appelle son point d'entrée.

C'est la seule fonction dans le code source qui n'a pas besoin d'être appelée dans le programme pour être exécutée. En effet, elle est automatiquement appelée par le chargeur de programme – un composant du système d'exploitation – au début du processus d'exécution (cf. chap. C4‑IV ).

Remarque : « main » est ici l'adjectif anglais qui signifie justement « principal ».

Dans le cadre d'un programme multi‑fichiers, le fichier source dans lequel la fonction main est enregistrée constitue le fichier principal d'implémentation du programme. L'usage le plus courant veut qu'il soit nommé main.c.

Mais lorsqu'un programme n'est constitué que d'un seul fichier source et qu'on le compile « à la volée » dans un terminal de commandes en ligne, il est pertinent de donner à ce fichier un nom plus explicite faisant référence à ce que fait le programme – par exemple, helloWorld.c – en prenant soin de conserver l'extension .c.

Par ailleurs, dès qu'un programme est constitué de plusieurs fichiers sources, il est recommandé de constituer un répertoire de projet pour les regrouper, ainsi que les fichiers objets et exécutables – cf. chap. C1‑II . Dans ce répertoire, il est alors très facile de repérer, parmi les fichiers sources, dans lequel est codée la fonction principale main si ce fichier est nommé main.c. C'est la procédure employée par défaut par certains IDE (notamment Code::Blocks W) lorsqu'on crée un nouveau projet.

  1. En fait, un fichier source peut même être nommé avec n'importe quelle extension. Mais, l'extension .c reste vivement recommandée car elle permet à l'éditeur de code ainsi qu'au compilateur de détecter automatiquement qu'il s'agit d'un fichier source en langage C. Également, cela le évite les confusions que le codeur pourrait faire avec d'autres fichiers codés en langage C++ – et ainsi ne pas se tromper de compilateur pour élaborer le programme exécutable.
  2. Dans le nom d'un fichier source, il est recommandé de ne pas utiliser l'espace ni la plupart des symboles (& # ? etc.) de même que les caractères spéciaux (saut de tabulation, etc.), au risque sinon de nuire à la bonne exécution des commandes de génération du programme exécutable avec une syntaxe usuelle.

Squelette de la fonction main

Le code source de la fonction main présente le « squelette » minimal de définition d'une fonction codé ci‑dessous :


int main(void)  // void -> no argument
{

  /* some code here */

  return 0;     // 0 = conventional value of normal ending
}

sachant que, par défaut :

  • on code la fonction main sans argument (on parle aussi de paramètre), d'où la liste vide explicite (void) (elle peut aussi se coder implicitement (), mais la forme explicite est à privilégier en langage C) ;
  • la fonction main retourne (c'est‑à‑dire, fournit comme valeur) un nombre entier, d'où :
    • le descripteur de type int (début du mot anglais integer qui signifie « entier »),
    • et l'instruction simple return 0; qui spécifie la valeur retournée, ici 0 ;
  • la valeur retournée par la fonction main est interprétée comme un code d'erreur, la valeur 0 signifiant par convention l'absence d'erreur, c'est‑à‑dire le fait que l'exécution du programme s'est terminée normalement.

Lorsqu'on compile et exécute un programme dans un terminal de commandes en ligne , le processus d'exécution n'affiche pas la valeur retournée par la fonction main. Néanmoins, il est possible d'obtenir cette valeur en saisissant juste après la commande :

  • echo $? sous Linux ;
  • echo $LastExitCode sous Windows PowerShell.

Attention. Dans les deux cas, il s'agit de valeur retournée par la dernière commande exécutée, donc, si on renouvelle cette commande, on n'obtient pas la même valeur, mais celle retournée par la commande echo.

  1. En langage C/C++, le type int des valeurs de retour de la fonction main est signé (cf. chap. C2‑III ) et de grande étendue. Mais sous Linux, la variable système $? l'enregistre dans le type unsigned char (cf. chap. S1‑III ). Autrement dit, dans un terminal de commandes en ligne :
  2.     
    echo $?
    ne peut afficher que les valeurs comprises entre 0 et 255. Dans ce contexte, si la fonction main d'un programme retourne un code d'erreur négatif comme par exemple -1, on obtient 255, comme pour un débordement traité en rebouclage sa valeur complémentaire à 256 (cf. chap. C3‑II ).
  3. Historiquement – jusqu'à la norme C90 incluse – le codage type des valeurs de retour int de la fonction main était facultatif car, il était attribué par défaut par le compilateur. Cette pratique est interdite depuis la norme C99, elle déclenche automatiquement une erreur de compilation.
  4. En revanche, en langage C et C++, le codage d'au moins une expression return dans la fonction main est optionnel. Pour autant, ce n'est pas une bonne pratique, car en cas d'absence, la valeur de retour du programme est indéterminée.
  5. De plus, ce n'est pas cohérent d'un point de vue syntaxique dès lors que l'on code int main( ) le début de la définition de la fonction main. En toute rigueur, si l'on souhaitait que la fonction main ne retourne aucune valeur, il serait préférable d'expliciter ce choix en codant son type de valeurs retournées void (cf. chap. C4‑I ), comme ci‑dessous :
    
    void main(void) // BAD PRACTICE in C, ERROR in C++
    {
      // no returned value
    }
    
    
    sachant que, même dans ce cas, des instructions return; pourraient éventuellement être codées dans le programme (cf. chap. C4‑I ). Toutefois :
    • en langage C, cette pratique est signalée par un avertissement juste avec l'option -Wall ;
    • en langage C++, cette pratique déclenche tout simplement une erreur de compilation.

Bloc de définition de la fonction main

Les accolades ouvrante { et fermante } délimitent le bloc de définition du corps de la fonction main. Ce bloc peut être composé d'autant d'instructions que nécessaire.


/* here, no ordinary statement can be coded
 * only directives, declarations and definitions
 */


int main(void) 
{ // <- BEGINNING of the "main" function definition bloc

  /* here, any kind of directives and statements 
   * can be coded 
   */

  return 0;
} // <- END of the "main" function definition bloc


/* here, no ordinary statement can be coded
 * only directives, declarations and definitions
 */

Hors du bloc de définition de la fonction main – aussi bien avant qu' après – les éléments de code du fichier source ne peuvent être que :

  • des directives destinées au préprocesseur – c'est le composant de la chaîne de compilation qui agit en premier sur le fichier source, avant même le compilateur (cf. chap. C1‑II ) ;
  • (Les directives sont faciles à repérer : elles sont codées par des lignes de contrôle toujours précédées par le symbole # dit croisillon.)
  • des déclarations de types, de variables, de fonctions ;
  • (Les déclarations commencent toujours par un descripteur de type, comme int, float, etc.)
  • d'autres définitions de fonctions (cf. infra ) ;

mais il ne peut pas y avoir d'instructions au sens strict du terme (cf. chap. C2‑II ), ni simples, ni structurées.

Toutes ces notions – directive, déclaration, définition – seront détaillées plus loin et dans d'autres chapitres du module.

  1. D'autres fonctions peuvent être définies avant main dans le fichier source principal, mais pour autant, elles ne constituent pas le point d'entrée du programme. Plus généralement, l'ordre de définition des fonctions dans un fichier source ne code pas du tout leur ordre d'exécution.
  2. Les accolades ne servent pas seulement à délimiter le bloc de définition de la fonction main, mais plus généralement à délimiter le bloc toute autre fonction ou de groupement d'instructions codées en langage C/C++.

Directives d'inclusion de modules de bibliothèque

Presque toujours, un fichier source en langage C commence, avant tout bloc de fonction, par des directives d'inclusion de fichiers d'en‑tête de modules de bibliothèque, de la forme :
#include <nom de fichier d'en‑tête> où le nom de fichier d'en‑tête codé comporte l'extension .h (pour header en anglais).

Ces directives permettent d'employer dans le fichier source où elles sont inscrites les éléments de code (types, variables, fonctions) définis dans les modules de bibliothèque spécifiés.

C'est un aspect essentiel de la modularité du langage C.

Le codage des directives est approfondi au chapitre C4‑III .

La directive #include <stdio.h> autorise dans le code source l'emploi des fonction d'entrées‑sorties standard sur un poste de travail, c'est‑à‑dire de saisie et d'affichage de texte dans le terminal d'exécution du programme sur un poste de travail :

  • la sortie standard étant la fenêtre du terminal sur l'écran de l'ordinateur ;
  • l'entrée standard le clavier de l'ordinateur.

C'est notamment le cas de la fonction printf qui est très utilisée pour afficher des messages dans la fenêtre du terminal. L'exemple le plus emblématique d'emploi est certainement le programme « Hello World » W que tout débutant commence par coder pour vérifier que son environnement de programmation fonctionne bien :

#include <stdio.h>

int main(void)
{
    printf("Hello, World!");
    return 0;
}

En langage C++

Comme en langage C, le code source d'un programme codé en langage C++ est articulé autour d'une fonction principale main qui constitue son point d'entrée. La définition de cette fonction possède donc presque le même squelette général que celui présenté supra .

La seule différence est l'en‑tête de la fonction main : en l'absence d'argument, l'usage veut qu'elle se code avec une liste vide, sans le mot‑clef void :

int main() // no argument -> empty list
{/* ... */}

Dans le cadre d'un programme multi‑fichier, il est d'usage que le fichier source principal qui contient la fonction main soit nommé main.cpp. L'extension .cpp (pour « C plus plus ») permet d'identifier le langage C++ employé, sans confusion possible avec le langage C.

Modules spécifiques de la bibliothèque standard

Une autre différence notable avec des programmes codés en langage C est le recours en langage C++ à des modules de bibliothèque standard spécifiques. Les fichiers d'en‑tête se distinguent de ceux du langage C par absence d'extension. C'est pourquoi on ne code pas de .h dans les directives d'inclusion de ces fichiers.

Pour disposer des fonctions d'entrées‑sorties standard du langage C++, on code usuellement la directive :
#include <iostream>

Par rapport au module stdio de la bibliothèque standard du langage C, le module iostream spécifique au C++ apporte les mêmes fonctionnalités d'entrées‑sorties mais avec une syntaxe complètement différente réputée plus lisible.

À titre d'exemple emblématique, en C++, le programme classique « Hello World » se code académiquement comme ci‑dessous :

#include <iostream>

int main()
{
  std::cout << "Hello World" << std::endl;
  return EXIT_SUCCESS;
}

Remarque. L'identificateur EXIT_SUCCESS est une pseudo‑constante (cf. chap. C4‑III ) qui prend la valeur 0, c'est‑à‑dire par convention le code de retour normal d'un programmee. Cette pseudo‑constante est définie dans le fichier d'en‑tête cstdlib (ainsi que dans stdlib.h – cf. remarque infra) qui est incorporé dans le programme par un jeu complexe de directives d'inclusion. Son utilisation apporte une meilleure lisibilité au code.

De même, il existe une autre pseudo‑constante nommée EXIT_FAILURE, qui prend la valeur 1, pour coder de façon plus lisible une sortie sur échec de la fonction main.

Il est tout à fait possible dans un programme codé en langage C++ d'utiliser des modules de la bibliothèque standard du C via des directives comme, par exemple #include <math.h>. Les fichiers du framework Arduino recourent beaucoup à cette pratique.

Néanmoins, il est recommandé d'employer préférentiellement les variantes spécialement développées dans la bibliothèque standard du C++. Par exemple, pour recourir aux fonctions mathématiques usuelles, la pratique recommandée consiste à coder la directive :
#include <cmath>
car cette variante procure des fonctions codées pour être plus robustes aux erreurs par rapport à celles déclarées dans le fichier d'en‑tête math.h de la bibliothèque standard du langage C.

La notion de fonction

Généralités

En langages C et C++, en plus de la fonction principale main, un programme peut aussi comprendre des fonctions auxiliaires – et en pratique, on parle tout simplement de fonction sans faire mention de leur caractère « auxiliaire ».

Comme la fonction main, toute fonction s'insère dans un programme en codant dans l'ordre :

  • son type de valeurs de retour (void s'il n'y en a pas) ;
  • son nom (un identificateur choisi par le codeur) :
  • sa liste d'arguments (c'est‑à‑dire ses paramètres), qui est éventuellement vide ;
  • son corps de définition (le plus souvent, un bloc d'instructions).

La notion de fonction auxiliaire correspond ce qu'on appelle communément un sous‑programme, c'est‑à‑dire une partie de code déportée de la fonction principale (voire d'une autre fonction auxiliaire) afin d'alléger le code de cette dernière.

Supposons que l'on veuille enjoliver le programme académique « Hello World » pour obtenir le résultat suivant :

*****************
* Hello, World! *
*****************

Pour ne pas se tromper dans le nombre d'étoiles à afficher en haut et en bas du cadre ainsi tracé, une solution consiste à coder une fonction auxiliaire qui affiche une ligne de caractères « * ». Typiquement, on peut nommer cette fonction printStarline (cf. les lignes nº 3 à 6 ci‑dessous).

Le programme devient alors :

#include <stdio.h>

void printStarline(void)
{
  printf("*****************\n");
}

int main(void)
{
  printStarline();
  printf("* Hello, World! *\n");
  printStarline();
  return 0;
}

Appel d'une fonction

Contrairement à la fonction main, pour être exécutée dans un programme, toute fonction auxiliaire doit impérativement faire l'objet d'un appel. Il s'agit d'une expression dont la forme syntaxique générale est :
nom de la fonction(liste des valeurs d'arguments)

Si la fonction n'a pas d'argument, on code une liste vide (comme lors de sa déclaration – cf. supra ), les parenthèses () étant obligatoires dans tous les cas. En effet, en plus de servir à encapsuler la liste des valeurs des arguments, les parenthèses constituent aussi le symbole de l'opérateur d'appel de fonction – cf. chap. C2‑IV 

Dans un programme, on peut coder plusieurs appels d'une fonction auxiliaire (avec éventuellement des valeurs différentes pour les arguments), à condition :

  • que la fonction appelée ait fait l'objet d'une déclaration préalable ;
  • que tout appel soit lui même codé dans une fonction (par exemple, la fonction main) – dite fonction appelante.

Le fait d'avoir plusieurs appels d'une même fonction dans un programme est une bonne raison pour coder ses instructions séparément de la fonction appelante ; sinon, elles devraient se répéter dans la fonction appelante. On parle de factorisation du code.

Lors de l'exécution de l'appel d'une fonction auxiliaire :

  • le processus de la fonction appelante s'interrompt ;
  • le processus la fonction appelée s'exécute ;
  • le processus de fonction appelante reprend juste après.

Dans la fonction main du programme « Hello World » enjolivé supra , on trouve deux appels de la fonction printStarline, aux lignes nº 10 & 12 :

int main(void)
{
  printStarline();
  printf("* Hello, World! *\n");
  printStarline();
  return 0;
}

La question de la transmission des arguments d'une fonction lors d'un appel est complexe. Elle sera abordée aux chapitres C4‑I  et C5‑II .

Déclaration séparée d'une fonction (notion de prototype)

La condition de coder toute fonction auxiliaire avant son appel présente un inconvénient : on pousse le code de la fonction main tout à la fin du fichier source. En termes de lisibilité, cela n'est pas satisfaisant car, rappelons‑le, la fonction main est le point d'entrée du programme. Il est donc essentiel de pouvoir lire cette fonction en priorité pour bien comprendre ce que fait le programme.

Pour y remédier, les langages C et C++ ont introduit le concept de prototype d'une fonction. Il s'agit d'une déclaration de la fonction qui reprend sa ligne d'en‑tête seule, c'est‑à‑dire une instruction de la forme :
type nom de la fonction(liste des arguments);
donc, terminée par le séparateur ; et sans son bloc de définition.

Avec cette notion de prototype, on peut déclarer d'abord une fonction pour pouvoir l'appeler avant le code de sa définition – à condition bien‑sûr que cette fonction soit définie plus loin dans le programme.

Le programme « Hello World » enjolivé supra  peut donc aussi se coder en commençant par la déclaration de la fonction printStarline, c'est‑à‑dire le codage de son prototype, à la ligne nº 3. Cela permet de coder sa définition (avec son bloc d'instructions) après celle de la fonction main, comme ci‑dessous.

#include <stdio.h>

void printStarline(void); // function declaration (prototype)

int main(void)
{
  printStarline();
  printf("* Hello, World! *\n");
  printStarline();
  return 0;
}

void printStarline(void) // function definition
{
  printf("*****************\n");
}

Remarque. La fonction printf peut être appelée dans la fonction main parce qu'elle est déjà déclarée dans le fichier d'en‑tête stdio.h, dont la directive d'inclusion est codée au tout début du fichier (donc, avant la définition de fonction main).

Cas d'un programme Arduino

Squelette d'un programme Arduino

Les fonctions setup et loop

Dans un programme Arduino, on ne code pas de fonction main mais deux autres fonctions :

  • la fonction setup qui constitue, en apparence pour le codeur, le point d'entrée du programme ; elle est destinée à être exécutée une seule fois ;
  • puis la fonction loop qui destinée à être exécutée en boucle, indéfiniment (implicitement, une structure de contrôle for(;;) – cf. chap. C2‑V ), dès que l'exécution de la fonction setup est achevée.

Le squelette minimal d'un programme Arduino est donc constitué de la définition respective de ces deux fonctions :


/* here, no ordinary statement can be coded
 * only directives, declarations and definitions
 */


void setup() 
{ // <- BEGINNING of the "setup" function def. bloc

  /* here, any kind of directives and statements 
   * can be coded 
   */

} // <- END of the "setup" function def. bloc


/* here, no ordinary statement can be coded
 * only directives, declarations and definitions
 */


void loop() 
{ // <- BEGINNING of the "loop" function def. bloc

   /* here, any kind of directives and statements 
   * can be coded 
   */

} // <- END of the "loop" function def. bloc


/* here, no ordinary statement can be coded
 * only directives, declarations and definitions
 */

Ces deux fonctions sont de type void (cf. chap. C3‑I ). Sauf cas très particuliers, il ne faut surtout pas coder d'instruction return dans leur bloc de définition.

Le fichier principal d'extension .ino et son répertoire homonyme

En Arduino, un programme est compilé en langage C++, mais le code source des fonctions setup et loop doit être stocké dans un fichier principal d'extension .ino.

De plus, ce fichier source doit obligatoirement être placé dans un répertoire de projet homonyme, c'est‑à‑dire ayant un nom de base identique (same basename), mais sans extension.

C'est par cette homonymie que procède l'identification automatique du fichier principal, car le répertoire de projet peut éventuellement contenir d'autres fichiers sources d'extension .ino (ainsi que des fichiers .cpp .h …), dans le cadre d'une programmation multi‑fichiers.

Comme dans le fichier source principal d'un programme en C ou C++ (cf. supra ), hors des blocs de définition des fonctions setup et loop, il ne peut y avoir que des  directives, des déclarations et des définitions, mais il ne peut pas y avoir d'instructions au sens strict du terme, ni simples, ni structurées.

Fonctionnement du logiciel Arduino IDE

Lorsqu'on clique sur le bouton Téléverser (upload – cf. l'icône ci‑contre) pour installer le programme utilisateur sur la carte, le logiciel Arduino IDE met en œuvre une chaîne de compilation GCC en langage C++, qui est spécifiquement construite pour l'architecture du microcontrôleur de la carte (cf. chap. C1‑III ).

Pour cela, le logiciel Arduino IDEil accomplit plusieurs tâches en arrière plan.

Essentiellement, il compile un fichier déjà codé nommé main.cpp, issu du noyau du framework (cf. G pour les microcontrôleurs à cœur AVR). Dans ce fichier se trouve le code source de définition de la fonction main où l'on observe :

  • que la fonction setup n'est appelée qu'une seule fois A ;
  • que la fonction loop est appelée dans une structure de boucle sans fin (forever), ce qui fait qu'après exécution de sa dernière instruction, elle reprend à sa première instruction, etc. A.
Ceci explique comment est implémenté l'algorithme fondamental d'un programme Arduino, dont l'algorigramme est donné supra .

De plus, le fichier main.cpp fourni par logiciel Arduino IDE contient toujours la directive :

#include <Arduino.h>

Cette directive inclut le module principal (on parle de noyau – en anglais, core) de la bibliothèque Arduino qui déclare et définit les éléments de code spécifiques pour les cartes à microcontrôleur de la marque ou compatibles.

Le fichier d'en‑tête Arduino.h (cf. G pour les microcontrôleurs à cœur AVR) lui‑même contient d'autres directives d'inclusion :

  • de nombreux autres modules de la bibliothèque Arduino ;
  • quelques modules très usuels de la bibliothèque standard du langage C :
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <math.h>

Enfin, le fichier Arduino.h déclare par leur prototype (cf. supra ) les fonctions setup et loop (aux lignes nº 154 & 155).

void setup(void);
void loop(void);

Le fichier d'en‑tête stdio.h n'est jamais inclus par une directive dans les fichiers d'en‑tête du framework Arduino. En effet, de façon générale, une carte à microcontrôleur n'est pas un poste de travail :

  • elle n'a ni écran, ni clavier,
  • elle n'a pas de système d'exploitation pour gérer un terminal d'exécution.

Elle n'est donc pas conçue pour mettre en œuvre des entrées‑sorties standards.

En revanche, une carte Arduino dispose d'autres moyens d'entrées‑sorties via son port numérique, son port analogique, et divers bus de communication permettant de la relier à des dispositifs de saisie et d'affichage, comme l'écran à affichage LCD en photo ci‑contre.

Également, une carte Arduino dispose d'au moins un port série qui permet, typiquement en phase de développement, d'échanger des données textuelles avec l'environnement de programmation. Mis œuvre par le biais de fonctions spécifiques (Serial.print, etc.), ces aspects seront étudiés notamment au chapitre C3‑X .