Les .ino et leur compilation

Une petite expérience inspirée de :
organisation clair d'un code
Recette de cuisine pour la compilation séparée

Quand on compile un .ino, comment fait le compilateur pour :

Trouver les fonctions appelées dans setup() ou loop() si elles sont définies en fin de fichier ?

Trouver les fonctions situées dans d'autres .ino sans prototype ?

Trouver les variables situées dans d'autres .ino sans déclaration "extern" ?

Normalement, en compilation C classique il faudrait que les fonctions appelées soient définies en début de fichier, avant leur appel donc, ou qu'un prototype soit présent en début de fichier ou dans un .h inclus.
Idem pour les variables.

Quand on compile un ou plusieurs .ino que se passe t-il ?

Prenons un exemple :

main.ino :

int counter;

void setup() {
  initialize();
}

void loop() {
  test();
  go();
}

void go()
{
  delay(1000);
}

init.ino :

void initialize()
{ 
}

test.ino :

void test(void)
{
  Serial.println(counter++);
}

D'un point de vue C classique, on peut remarquer :

  • aucun prototype nulle part
  • la variable counter est utilisée par la fonction test() sans déclaration "extern"

Normalement, la compilation devrait échouer.
Mais non :

Detecting libraries used...
"/home/riton/.arduino15/packages/arduino/tools/avr-gcc/5.4.0-atmel3.6.1-arduino2/bin/avr-g++" -c -g -Os -w -std=gnu++11 -fpermissive -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics -flto -w -x c++ -E -CC -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=10805 -DARDUINO_AVR_NANO -DARDUINO_ARCH_AVR "-I/home/riton/.arduino15/packages/arduino/hardware/avr/1.6.207/cores/arduino" "-I/home/riton/.arduino15/packages/arduino/hardware/avr/1.6.207/variants/eightanaloginputs" "/tmp/arduino_build_934234/sketch/main.ino.cpp" -o "/dev/null"
Generating function prototypes...
"/home/riton/.arduino15/packages/arduino/tools/avr-gcc/5.4.0-atmel3.6.1-arduino2/bin/avr-g++" -c -g -Os -w -std=gnu++11 -fpermissive -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics -flto -w -x c++ -E -CC -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=10805 -DARDUINO_AVR_NANO -DARDUINO_ARCH_AVR "-I/home/riton/.arduino15/packages/arduino/hardware/avr/1.6.207/cores/arduino" "-I/home/riton/.arduino15/packages/arduino/hardware/avr/1.6.207/variants/eightanaloginputs" "/tmp/arduino_build_934234/sketch/main.ino.cpp" -o "/tmp/arduino_build_934234/preproc/ctags_target_for_gcc_minus_e.cpp"
"/mnt/sdc1/riton/arduino-1.8.5/tools-builder/ctags/5.8-arduino11/ctags" -u --language-force=c++ -f - --c++-kinds=svpf --fields=KSTtzns --line-directives "/tmp/arduino_build_934234/preproc/ctags_target_for_gcc_minus_e.cpp"
Compilation du croquis...
"/home/riton/.arduino15/packages/arduino/tools/avr-gcc/5.4.0-atmel3.6.1-arduino2/bin/avr-g++" -c -g -Os -w -std=gnu++11 -fpermissive -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics -MMD -flto -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=10805 -DARDUINO_AVR_NANO -DARDUINO_ARCH_AVR "-I/home/riton/.arduino15/packages/arduino/hardware/avr/1.6.207/cores/arduino" "-I/home/riton/.arduino15/packages/arduino/hardware/avr/1.6.207/variants/eightanaloginputs" "/tmp/arduino_build_934234/sketch/main.ino.cpp" -o "/tmp/arduino_build_934234/sketch/main.ino.cpp.o"
Compiling libraries...
Compiling core...
Using precompiled core
Linking everything together...
"/home/riton/.arduino15/packages/arduino/tools/avr-gcc/5.4.0-atmel3.6.1-arduino2/bin/avr-gcc" -w -Os -g -flto -fuse-linker-plugin -Wl,--gc-sections -mmcu=atmega328p -o "/tmp/arduino_build_934234/main.ino.elf" "/tmp/arduino_build_934234/sketch/main.ino.cpp.o" "/tmp/arduino_build_934234/../arduino_cache_411670/core/core_arduino_avr_nano_cpu_atmega328_cb8b63e61353f69f40ab69233ebc372f.a" "-L/tmp/arduino_build_934234" -lm
"/home/riton/.arduino15/packages/arduino/tools/avr-gcc/5.4.0-atmel3.6.1-arduino2/bin/avr-objcopy" -O ihex -j .eeprom --set-section-flags=.eeprom=alloc,load --no-change-warnings --change-section-lma .eeprom=0 "/tmp/arduino_build_934234/main.ino.elf" "/tmp/arduino_build_934234/main.ino.eep"
"/home/riton/.arduino15/packages/arduino/tools/avr-gcc/5.4.0-atmel3.6.1-arduino2/bin/avr-objcopy" -O ihex -R .eeprom "/tmp/arduino_build_934234/main.ino.elf" "/tmp/arduino_build_934234/main.ino.hex"
Le croquis utilise 1728 octets (5%) de l'espace de stockage de programmes. Le maximum est de 30720 octets.
Les variables globales utilisent 190 octets (9%) de mémoire dynamique, ce qui laisse 1858 octets pour les variables locales. Le maximum est de 2048 octets.

Tout d'abord, on peut remarquer quelques petites chose :

init.ino et test.ino n'apparaissent nulle part.
Generating function prototypes... (tiens, tiens ...)
Compilation du croquis... (on parle de croquis, pas de plusieurs ...)

Allons voir ce croquis :
/tmp/arduino_build_934234/sketch/main.ino.cpp :

#include <Arduino.h>
#line 1 "/mnt/sdc1/riton/Arduino/main/main.ino"
#line 1 "/mnt/sdc1/riton/Arduino/main/main.ino"

int counter;

#line 4 "/mnt/sdc1/riton/Arduino/main/main.ino"
void setup();
#line 8 "/mnt/sdc1/riton/Arduino/main/main.ino"
void loop();
#line 13 "/mnt/sdc1/riton/Arduino/main/main.ino"
void go();
#line 2 "/mnt/sdc1/riton/Arduino/main/init.ino"
void initialize();
#line 2 "/mnt/sdc1/riton/Arduino/main/test.ino"
void test(void);
#line 4 "/mnt/sdc1/riton/Arduino/main/main.ino"
void setup() {
  initialize();
}

void loop() {
  test();
  go();
}

void go()
{
  delay(1);
}


#line 1 "/mnt/sdc1/riton/Arduino/main/init.ino"

void initialize()
{ 
}


#line 1 "/mnt/sdc1/riton/Arduino/main/test.ino"

void test(void)
{
  Serial.println(counter++);
}

Quand on compile un projet comprenant plusieurs .ino, l'IDE regroupe tout dans un seul .cpp, en ajoutant les petites choses qui manquent :

  • #include <Arduino.h>
  • les prototypes des fonctions

Par contre il n'ajoute aucune déclaration "extern" des variables.

On peut en tirer quelques conclusions :

Toutes les fonctions pourront être appelées à partir de n'importe fonction de n'importe quel fichier.

La variable counter ne sera vue de tous les autres fichiers et fonctions que si elle est placée dans le .ino principal et pas ailleurs.

Une variable déclarée dans un autre .ino que le .ino principal ne pourra être vue que par les fonction du même fichier ou des fichiers suivants (dans l'ordre des onglets), à moins de déclarer un "extern" de cette variable dans les fichiers précédents.

Assez pittoresque non ?
Et assez contraignant somme toute.
Assez difficile d'imaginer une vraie modularité dans ces conditions.

Il est clair que cette façon de travailler ne fonctionne que dans l'IDE ARDUINO et ne marchera pas dans un autre IDE, PlatformIO entre autres.

La seule façon correcte de procéder est celle qui consiste à créer non pas plusieurs .ino dans son projet mais un seul .ino (ou main.cpp dans PlatformIO) et plusieurs .cpp et .h

Cela permet entre autres de développer ses logiciels avec PlatformIO, un IDE pourvu de toutes les fonctionnalités que l'on attend d'un outil moderne, et que j'utilise pour tous mes développements conséquents.

Et cela permet aussi d'exporter facilement un .cpp avec son .h associé pour en faire une librairie.

Cordialement
Henri

Pour compléter :

  1. Multi fichier ino
    C'est possible avec l'IDE arduino (en réalité processing) mais il faut savoir qu'au final il n'y aura qu'un seul fichier ino qui sera traité par l'IDE Arduino.
    Les différents fichiers ino sont concaténés par ordre alphabétique ce qui peut poser quelques problèmes.
    Imaginons le cas d'école suivant .
    a) le fichier ino "principal" est utilisé pour les déclarations,
    b) on créé un fichier setup.ino et un fichier loop.ino.
    Cela compile mais comme alphabétiquement "l" est avant "s" le programme résultant appelera la fonction loop() avant la fonction setup() qui donc ne sera jamais appelée.
    On peut s'en sortir en choisissant "intelligeament" les noms de fichiers.
    Cela fonctionnera correctement avec setup.ino renommé en A_setup().ino et loop().ino renommé en B_loop.ino.

  2. Qu'est-ce qui est transmis au compilateur ?
    En aucun cas le compilateur ne reçoit un fichier ino mais un fichier c/cpp.
    La structure de ce fichier que l'IDE va créer à partir du fichier ino est la suivante:

//zone des déclaration personelles
#include>Arduino.h>

void main()
{
  init();
  setup();
  while (1)
  {
    loop();
  }
}
// fonctions utilisateurs appelées à partir de loop().

Arduino.h contient en autre les prototypes des fonctions Arduino
init() est la fonction qui configure le micro "à la mode Arduino", c'est init() qui fixe le timer 0 pour les fonctions de temps ou la fréquence de PWM par exemple.
While(1) est la boucle dont on ne peut pas sortir car la condition est toujours vraie. En fait c'est une boucle for qui est plus rapide qu'un while mais le while est plus facile à comprendre.
L'IDE arduino détecte aussi les fonctions utilisateurs contenues dans le fichier ino et crée leur prototype.
Conclusion :
Dans le fichier ino l'IDE travaille pour vous sans le dire, ce qui ne sera plus le cas avec un fichier cpp
Un compilateur ne compile que des fichiers individuels et le compilateur doit connaitre dès le début tout les prototype de fonction. Dans les fichiers cpp il ne faut pas oublier d'inclure Arduino.h qui contient les prototypes des fonctions arduino et les définitions "Arduino"

Le mieux est de suivre un tuto d'initiation au C/C++. On en trouve qui sont simple d'accès.
La lecture du code d'Arduino.h et d'init() que l'on trouve dans le "core" de L'IDE est très enrichissante.

Cela compile mais comme alphabétiquement "l" est avant "s" le programme résultant appelera la fonction loop() avant la fonction setup() qui donc ne sera jamais appelée.

Impossible. La fonction setup() est toujours appelée en premier par la fonction main() qui appelle ensuite la fonction loop() en boucle. Peu importe le nom des fichiers.

A moins que je n'aie pas compris la phrase ... ou que la phrase soit mal formulée.

Le premier onglet, quelque soit son nom, porte toujours le même nom que le répertoire où il se trouve.
L'IDE ARDUINO place toujours ce fichier dans le premier onglet, même s'il s'appelle zzzzz.ino

Alors je re-explique :

  • Il y a le fichier ino que j'appelle fichier principal et qui porte le nom du répertoire : c'est le mode normal avec processing/wiring/arduino.

On va dire que le répertoire s'appelle "machin" et qu'à l'intérieur il y a le fichier "machin.ino"
Il y a les onglets qu'on peut ajouter.

Soit ce sont des fichiers ino soit ce sont des fichiers *.cpp et leur compagnon *.h

  • Si ce sont des fichiers cpp c'est clair : les règles du C s'appliquent.
  • Si ce sont des fichiers ino l'IDE les concatène avec le fichier "principal" machin.ino dans l'ordre alphabétique des noms de fichiers.
    C'est un choix de fonctionnement de l'IDE, il faut faire avec.

J'ai pris un cas d'école avec un raisonnement par l'absurde : si on ne prend pas en compte ce fonctionnement on peut arriver a ce que la fonction loop() soit appelée avant le setup() parce qu'on aura mis la fonction loop() dans un onglet loop.ino et la fonction setup() dans un onglet setup.ino et que l'opération de concaténation ajoutera loop.ino au fichier machin.ino avant d'ajouter setup.ino.
Cela parait absurde de faire ainsi mais c'est pour la démonstration et dans la vraie vie des cas moins évidents peuvent se produire d'où l'utilité de comprendre le mécanisme.

Le compilateur ne trouvera rien à redire puisqu'au final il ne verra qu'un seul fichier ino où la fonction loop() sera placée avant la fonction setup().
Bien évidement cela ne fonctionnera pas. Toutefois il n'y aura aucune alarme car le compilateur ne détecte que les erreurs de syntaxe par celles de raisonnement.

Il n'y a aucune raison que cela ne fonctionne pas, le main.cpp appelle setup() avant loop() dans tout les cas.

Les différents fichiers des onglets ino sont ajoutés ligne à ligne à la suite du programme machin.ino, fichiers ino par fichier ino pris par ordre alphabétique.
loop.ino et setup.ino ne sont que des nom de fichiers ino.

C'est une explication par l'absurde pour monter que l'utilisation de fichiers ino dans les onglets si elle peut paraître plus simple et plus rassurante que de faire appel à des fichiers *.cpp et leur compagnon *.h n'est pas exempte de pièges.
Dans la vraie vie il ne viendrait pas "normalement" pas à l'idée de mettre les fonctions setup() et loop() chacune dans un onglet, bon il y a un mec un peu tordu qui a essayé :grin:.

Code du fichier machin.ino

// commentaires sur le programme
// explications diverses

// déclaration de variables

code du fichier setup.ino

void setup()
{
  // code du fichier setup
}

Code du fichier loop.ino

void loop()
{
   // code de loop
}

le fichier résultant machin.ino après concaténation sera

// commentaires sur le programme
// Explications diverses
// Déclarations de variables

//concaténation du premier fichier par ordre alphabétique qui est loop.ino
void loop()
{
}
//concaténation du fichier suivant par ordre alphabétique
void setup()
{
}

Et le fichier transmis au compilateur sera :

// déclaration de variable
// prototype créés par l'IDE
#include <Arduino.h|
void main()
{
   while(1)   { loop() }
   setup();
}
void  loop()
{// code
}
void setup()
{
  // code
}

Par contre si on fait le bon choix pour les noms des fichier ino contenus dans les onglets tout se passe bien.
D'un autre coté si on est pas tordu, comme je l'ai été pour voir, et qu'on laisse sagement setup() et loop() dans le fichier principal et que les onglets ne servent que pour les fonctions utilisateurs , normalement, il ne devrait pas y avoir de pièges.
Mais c'est quand même bon de comprendre le mécanisme utilisé par l'IDE.

Le code de main.cpp se trouve dans le core Arduino (.arduino15/packages/arduino/hardware/avr/1.6.207/cores/arduino)

int main(void)
{
	init();
	initVariant();
#if defined(USBCON)
	USBDevice.attach();
#endif
	setup();
	for (;;) {
		loop();
		if (serialEventRun) serialEventRun();
	}
	return 0;
}

La fonction setup() est forcément appelée en premier.

Un répertoire zzzzz avec :

zzzzz.ino :

int counter;

void go()
{
  delay(1000);
}

loop.ino :

void loop() {
  Serial.println("loop");
  test();
  go();
}
void test(void)
{
  Serial.println(counter++);
}

setup.ino :

void setup() {
  Serial.begin(115200);
  initialize();
}

void initialize()
{ 
  Serial.println("initialize");
}

Exécution :

initialize
loop
0
loop
1
loop
2
loop
3
loop
4
loop
5
loop
6
loop
7
loop
8

Les .ino sont concaténés dans un seul .cpp : /tmp/arduino_build_668436/sketch/zzzzz.ino.cpp

#include <Arduino.h>
#line 1 "/home/riton/Arduino/zzzzz/zzzzz.ino"
#line 1 "/home/riton/Arduino/zzzzz/zzzzz.ino"

int counter;

#line 4 "/home/riton/Arduino/zzzzz/zzzzz.ino"
void go();
#line 2 "/home/riton/Arduino/zzzzz/loop.ino"
void loop();
#line 8 "/home/riton/Arduino/zzzzz/loop.ino"
void test(void);
#line 2 "/home/riton/Arduino/zzzzz/setup.ino"
void setup();
#line 7 "/home/riton/Arduino/zzzzz/setup.ino"
void initialize();
#line 4 "/home/riton/Arduino/zzzzz/zzzzz.ino"
void go()
{
  delay(1000);
}


#line 1 "/home/riton/Arduino/zzzzz/loop.ino"

void loop() {
  Serial.println("loop");
  test();
  go();
}

void test(void)
{
  Serial.println(counter++);
}


#line 1 "/home/riton/Arduino/zzzzz/setup.ino"

void setup() {
  Serial.begin(115200);
  initialize();
}

void initialize()
{ 
  Serial.println("initialize");
}

La fonction setup() se retrouve bien après la fonction loop() dans le sketch résultant, mais je ne vois pas en quoi cela change quoi que ce soit pour la fonction main().

C'est une manip que j'ai fait il y a environ 4 ans et cela ne fonctionnait pas avec l'IDE de l'époque, ce devait être une 1.6.quelquechose.
Je ne l'ai plus jamais recommencé, depuis j'ai appris à écrire des couples de fichiers *.cpp et *.h.

A l'époque ma conclusion a été que le fichier main.cpp devait être mal construit et qu'on devait entrer directement dans la boucle infinie. Trouver de quelle manière il devait être mal construit dépasse mes compétences.

En renommant les fichiers des onglets pour forcer l'ordre alphabétique cela s'est remis à fonctionner.
Je n'ai pas décortiqué l'IDE pour en comprendre plus : c'est du java, je ne connaît pas.

Rien n'empêche de refaire la manip avec l'IDE 1.8.quelquechose pour voir si c'est pareil ou non.

Les .ino sont concaténés dans un seul .cpp : /tmp/arduino_build_668436/sketch/zzzzz.ino.cpp

Non zzzzz.ino.cpp n'est pas encore un fichier cpp mais toujours un fichier ino.
Regardes la fonction loop() elle n'est pas encore placée dans sa boucle infinie.

Je n'ai jamais trouvé le fichier cpp qui est envoyé au compilateur.
Si quelqu'un sait où il est je suis preneur.

Regardes la fonction loop() elle n'est pas encore placée dans sa boucle infinie.

La boucle infinie est dans la fonction main qui appelle loop() dans une boucle :

	for (;;) {

La compilation donne ceci :

Compilation du croquis...
"/home/riton/.arduino15/packages/arduino/tools/avr-gcc/5.4.0-atmel3.6.1-arduino2/bin/avr-g++" -c -g -Os -w -std=gnu++11 -fpermissive -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics -MMD -flto -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=10805 -DARDUINO_AVR_NANO -DARDUINO_ARCH_AVR "-I/home/riton/.arduino15/packages/arduino/hardware/avr/1.6.207/cores/arduino" "-I/home/riton/.arduino15/packages/arduino/hardware/avr/1.6.207/variants/eightanaloginputs" "/tmp/arduino_build_453418/sketch/zzzzz.ino.cpp" -o "/tmp/arduino_build_453418/sketch/zzzzz.ino.cpp.o"
Linking everything together...
"/home/riton/.arduino15/packages/arduino/tools/avr-gcc/5.4.0-atmel3.6.1-arduino2/bin/avr-gcc" -w -Os -g -flto -fuse-linker-plugin -Wl,--gc-sections -mmcu=atmega328p -o "/tmp/arduino_build_453418/zzzzz.ino.elf" "/tmp/arduino_build_453418/sketch/zzzzz.ino.cpp.o" "/tmp/arduino_build_453418/core/core.a" "-L/tmp/arduino_build_453418" -lm

Un objet zzzzz.ino.cpp.o est généré à partir de zzzzz.ino.cpp
Cet objet est utilisé pour l'édition de lien.

Rien de plus clair.

J'ai récupéré une nano et j'essaye de reproduire le défaut.
Avec la version : 1.8.3 -> je constate que je ne retrouve pas le défaut.

machin.ino

const uint8_t del = 13;

loop.ino

void loop() {
  digitalWrite(del, 1); 
  delay(200);                    
  digitalWrite(del, 0);    
  delay(200);                     
}

setup.ino

void setup() {
  pinMode(del, OUTPUT);
}

une fonction ajoutée :

void util(){
  // je ne fais rien
}

et le fichier machin.ino.cpp

#include <Arduino.h>
#line 1 "/home/b/Arduino/machin/machin.ino"
#line 1 "/home/b/Arduino/machin/machin.ino"
#line 1 "/home/b/Arduino/machin/loop.ino"
void loop();
#line 1 "/home/b/Arduino/machin/perso.ino"
void util();
#line 1 "/home/b/Arduino/machin/setup.ino"
void setup();
#line 1 "/home/b/Arduino/machin/loop.ino"
const uint8_t del = 13;

#line 1 "/home/b/Arduino/machin/loop.ino"
void loop() {
  digitalWrite(del, 1); 
  delay(200);                    
  digitalWrite(del, 0);    
  delay(200);                     
}

#line 1 "/home/b/Arduino/machin/perso.ino"
void util(){
  // je ne fais rien
}
#line 1 "/home/b/Arduino/machin/setup.ino"
void setup() {
  pinMode(del, OUTPUT);
}

Comme on peut le constater c'est un fichier qui a bien une extension extension cpp mais il ne contient pas de fonction main() ni le prototype de la fonction util().

C'est encore un fichier intermédiaire, ce n'est pas encore le vrai fichier cpp qui sera présenté au compilateur.
Tous les tutos de C que j'ai lu disent que la fonction main() est obligatoire en C.
Je ne pense pas que ce fichier machin.ino.cpp compile.

Je n'ai encore trouvé le vrai fichier cpp.

Avec la version : 1.6.5
La 1.6.5 est restée longtemps la seule version utilisable de la série des 1.6.x
Je l'ai re-chargé pour faire le test dans les mêmes conditions.

Malheureusement elle ne fonctionne plus dans ma Debian Stretch
Au moment du chargement du programme dans la nano j'ai le message d'erreur suivant :

Le croquis utilise 1 030 octets (3%) de l'espace de stockage de programmes. Le maximum est de 30 720 octets.
Les variables globales utilisent 9 octets (0%) de mémoire dynamique, ce qui laisse 2 039 octets pour les variables locales. Le maximum est de 2 048 octets.
/hca/30_IDE_arduino/arduino-1.6.5-r5/hardware/tools/avr/bin/avrdude: error while loading shared libraries: libreadline.so.6: cannot open shared object file: No such file or directory

Je ne peux pas faire la manip que je voulais pour contrôler avec la version de l'IDE précédemment utilisée.
Si je me suis mis le doigt dans l'œil jusqu'au coude j'aimerai bien qu'on m'explique.

C'est encore un fichier intermédiaire, ce n'est pas encore le vrai fichier cpp qui sera présenté au compilateur.
Tous les tutos de C que j'ai lu disent que la fonction main() est obligatoire en C.
Je ne pense pas que ce fichier machin.ino.cpp compile.

Le code de main.cpp se trouve dans le core Arduino (.arduino15/packages/arduino/hardware/avr/1.6.207/cores/arduino)

La compilation :

"/home/riton/.arduino15/packages/arduino/tools/avr-gcc/5.4.0-atmel3.6.1-arduino2/bin/avr-g++" -c -g -Os -w -std=gnu++11 -fpermissive -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics -MMD -flto -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=10805 -DARDUINO_AVR_NANO -DARDUINO_ARCH_AVR "-I/home/riton/.arduino15/packages/arduino/hardware/avr/1.6.207/cores/arduino" "-I/home/riton/.arduino15/packages/arduino/hardware/avr/1.6.207/variants/eightanaloginputs" "/home/riton/.arduino15/packages/arduino/hardware/avr/1.6.207/cores/arduino/main.cpp" -o "/tmp/arduino_build_200929/core/main.cpp.o"

L'objet est ajouté à la librairie core.a :

"/home/riton/.arduino15/packages/arduino/tools/avr-gcc/5.4.0-atmel3.6.1-arduino2/bin/avr-gcc-ar" rcs "/tmp/arduino_build_200929/core/core.a" "/tmp/arduino_build_200929/core/main.cpp.o"

Ensuite core.a est utilisée lors de l'édition de liens :

Linking everything together...
"/home/riton/.arduino15/packages/arduino/tools/avr-gcc/5.4.0-atmel3.6.1-arduino2/bin/avr-gcc" -w -Os -g -flto -fuse-linker-plugin -Wl,--gc-sections -mmcu=atmega328p -o "/tmp/arduino_build_200929/zzzzz.ino.elf" "/tmp/arduino_build_200929/sketch/zzzzz.ino.cpp.o" "/tmp/arduino_build_200929/core/core.a" "-L/tmp/arduino_build_200929" -lm

Il n'y a rien qui manque.

C'est assez courant dans un framework d'avoir la fonction main() dans la librairie standard.
Cela évite aux développeurs d'avoir à écrire certaines lignes (init() entre autres).

La version 1.6.9 me donne le même résultat sur Ubuntu 16.04.

Très intéressant !

Au passage on perd également la possibilité de limiter la portée d'une variable globale/fonction au fichier qui la définit (mot clé static) puisqu'au final 1 seul fichier est compilé dans le cas de multiples ino.

Or c'est une capacité du C très utilisé en programmation modulaire : chaque module peut avoir ses propres variables globales et fonctions, invisibles de l'extérieur et donc ne polluant pas l'espace de nommage global.

par exemple dans les gros projets, chaque module peut posséder sa propre méthode test(). Pas possible avec des ino, mais contournable en faisant des fonctions test_bidule (avec bidule le nom du ino par exemple).

Un autre avantage des variables globales/fonctions statiques est de pouvoir masquer ses fonctions réservées à l'usage internes des modules (un type d'encapsulation).

Dans le cas de petits projets il y a moyen de faire avec la technique ino je pense, maintenant que tu as expliqué les règles du jeu :wink:

Au passage on perd également la possibilité de limiter la portée d'une variable globale/fonction au fichier qui la définit (mot clé static) puisqu'au final 1 seul fichier est compilé dans le cas de multiples ino.

Oui, bien vu.