Nouveau

Transformer un front montant en impulsion

Pour la réalisation de mon horloge Nixie, j’ai eu besoin de transformer un créneau en impulsion. En effet, pour incrémenter les minutes ou les heures, il y a deux solutions :

  1. On appuie sur le bouton poussoir correspondant, ce qui génère un bref signal à 1, et cela incrémente le bloc des minutes (ou des heures).
  2. Le bloc précédent (les secondes dans le cas des minutes, les minutes dans le cas des heures) arrive en fin de cycle (il va « bientôt » repasser à 00) et génère un signal à un pour réaliser l’incrément du bloc suivant.

Le problème est que tout est dans le « bientôt » et le signal venant du bloc précédent peut rester longtemps à 1. Problème : il y a une porte logique OU entre ces deux signaux et c’est un front montant en sortie de cette porte qui incrémente un bloc. Si le signal du bloc précédent est à 1, la sortie du OU est donc à 1 et l’appui le bouton n’a pas d’effet ! Je me retrouvais donc avec des instants assez longs où les boutons ne servaient à rien…

La première idée est de faire une logique compliquée pour faire en sorte qu’un appui sur le bouton alors que l’autre signal est déjà à 1 génère une transition vers 0 puis une nouvelle transition vers 1 mais c’est vraiment pas une bonne idée. La bonne idée est de transformer le créneau sortant d’un bloc précédent en impulsion et de faire rentrer cette impulsion dans la porte OU. Ainsi, le temps où le bouton est sans effet est négligeable et on devrait donc avoir l’impression que ça marche tout le temps.

Quand j’avais rencontré ce problème, j’avais demandé à un collègue s’il connaissait une solution pour réaliser cette opération, et du fond de sa mémoire bien remplie mais n’ayant pas fait d’électronique analogique depuis un moment, il m’a sorti ce schéma :

impulsion-idee

L’idée était là, il fallait maintenant trouver le schéma exact et les bonnes valeurs de composants. Quelques recherches m’ont amené sur ce Cours de mécanique et électromagnétisme écrit par Jean-Louis Cayrel de l’IUT de Saint-Étienne. Dans la section 1.11.6  Utilisation des condensateurs, il y a un point appelé Transformation Front → Impulsion qui explique très bien le principe du montage. Il s’agit d’un filtre passe-haut : la capacité laisse d’abord passer le front montant, cette transition brutale étant une haute fréquence, puis se charge à travers la résistance jusqu’à bloquer le passage du courant quand il n’y a plus de variation du signal, coupant ainsi les basses fréquences. On obtient une impulsion ! Lorsque le signal repasse à 0, un phénomène similaire se produit mais l’impulsion est cette fois négative. La diode sert à presque supprimer les impulsions négatives puisqu’elle va limiter les tensions négatives à sa tension de seuil.

La charge de la capacité doit être rapide pour obtenir une impulsion qui ressemble vraiment à une impulsion et pas à une sorte de gros triangle. La durée de charge est dépend du très classique τ = R * C, la constante de temps d’un circuit RC. En effet, la fréquence de coupure du filtre est fc = 1 / τ = 1 / (R * C). Ainsi, plus τ sera petit, plus la fréquence de coupure sera grande et plus le filtre « coupera rapidement » le créneau.

Pour vérifier tout ça, j’ai bien sûr commencé par simuler mon schéma avec LTSPiceIV ! Voici le schéma :

impulsion-schema

J’ai fait le montage « en double », avec d’un côté des valeurs fixes et de l’autres côté une valeur fixe pour R mais variable pour C. Le seul intérêt de la partie de gauche est de pouvoir tracer Vout1 d’une couleur distincte de Vout2 et ainsi marquer clairement la courbe de référence, celle avec la valeur la plus faible de  τ, c’est-à-dire le filtre le plus « agressif ».

Le résultat de la simulation est le suivant :

impulsion-simulation

Je n’ai plus eu qu’à choisir les valeurs pour mon montage et tester ça sur plaquette d’essai. Pas de surprise : ça fonctionnait parfaitement ! J’ai donc mis un tel filtre entre mes blocs secondes et minutes entre mes blocs minutes et heures de mon horloge Nixie et mes boutons fonctionnaient nickel !

 

Overhead d’une classe et de son constructeur C++ sur une structure C

Il y a quelques temps, il y a eu une discussion assez animée sur Developpez.com autour des avantages de C++ par rapport à C. Comme trop souvent, des gens ont exposés des croyances et des convictions sans fondement ou a minima pas complètement vérifiées. Je débute dans mon utilisation professionnelle du C++, je développe sur micro-contrôleurs relativement contraints et surtout j’aime bien les preuves. Quand il a été dit que les constructeurs C++ étaient très coûteux et nécessitaient des mécanismes complexes, j’ai tiqué : sans avoir vérifié, je ne voyais rien qui pouvait justifier cela a priori. Un constructeur n’est souvent qu’une fonction qui initialise des variables…

Je suis donc allé sur Compiler Explorer (1), un site fort sympathique où vous écrivez du code source à gauche et où le code assembleur correspondant est généré à la volée à droite. Vous pouvez choisir parmi un nombre important de compilateurs dans différentes versions, changer les options de compilation et surtout, des blocs de couleurs font la correspondance entre les lignes du code source et les instructions assembleur correspondantes.

Puisque je travaille sur des interfaces graphiques, j’ai choisi de modéliser un écran avec seulement 2 propriétés : hauteur et largeur. J’ai donc écrit plusieurs codes C et C++ et comparé les codes assembleur générés. Je vous partage les captures d’écran faites depuis Compiler Explorer, vous pouvez cliquez dessus pour les ouvrir en taille réelle.

Pour commencer, voici une classe C++ très basique et très classique :

1-cpp-classe-et-constructeur

On voit tout de suite qu’un constructeur ne génère pas des tonnes d’assembleur et n’utilise pas de mécanique complexe. Comparons tout de suite cela à une structure C avec une fonction d’initialisation :

2-c-struct-et-fonction-creation

C’est donc confirmé : non, un constructeur C++ ne coûte pas les yeux de la tête comparé à une fonction C. Ici, il y a un peu plus d’assembleur mais ça ne change pas la face du monde (2).

Le hic est que ce code C n’est pas très idiomatique : on n’a pas besoin d’une fonction pour créer et initialiser une structure en C…. Voici donc un second code avec la même structure initialisée de manière classique, ainsi qu’une nouvelle fonction d’affichage :

3-c-idiomatic

L’initialisation de la structure tient maintenant en 2 instructions assembleur, c’est beaucoup mieux. Remarquez au passage qu’un appel de fonction (même vide), ça coûte ! Il faut maintenant trouver une alternative crédible en C++. On ne va se trainer un constructeur C++ si coûteux comparé à une bête liste entre accolades en C ! La première idée est celle d’une classe template. On a généralement un seul écran donc la duplication de code liée à la spécialisation des templates n’est pas un problème et la classe sera optimisée à la compilation pour les dimensions de l’écran (bon OK, ici, la classe est vide…). Voici ma classe template et son code assembleur :

4-cpp-template

Le code assembleur est le même que celui de la version C ! WTF ?! Un template C++ ça ne coûte pas super cher ? Et ben non : un template n’est rien de plus qu’une sorte de super macro. Quand on l’instancie, le contenu est copié et les paramètres formels sont remplacés par les arguments de l’instanciation.

Il y a une autre alternative en C++ dans le cas où la duplication du code n’est pas souhaitable : l’aggregate initialization. Pour que cela soit possible, il faut que la classe respecte certaines contraintes, notamment celui que ces champs soient publiques. On a alors un code source comme celui-ci, pour le même code assembleur généré :

5-cpp-aggregate-type

Pour conclure, on voit qu’une classe C++ simple n’est pas vraiment plus coûteux qu’une simple structure en C. Il y a des mécaniques C++ coûteuses, comme les fonctions virtuelles, les exceptions, mais vous n’êtes pas obligés de les utiliser si vous avez de vraies contraintes de tailles et de performances. On peut faire du code en C++ qui a des performances et une empreinte mémoire semblable à un code C, mais le C++ vous apporte des choses intéressantes que vous n’aurez pas en C. Le sujet de cet article n’est pas de lister ces choses intéressantes mais bien de montrer qu’il ne faut pas avoir d’a priori, dans un sens comme dans l’autre, et qu’il est nécessaire de tester, de se documenter et de vérifier.

(1) J’ai découvert ce site en regardant la vidéo de la conférence de Jason Turner à la CppCon 2016. Il montre comment il a codé le jeu pong sur Commodore64 en en C++17. Il utilise les fonctionnalités les plus avancées du langage et montre l’assembleur au fur et à mesure. Cette vidéo est incroyable et détruit le mythe qui voudrait que le C++ moderne et évolué ne permet pas de produire du code compact ! À regarder absolument !

(2) Vous remarquerez que les codes ci-dessus ont été compilés en -O0, c’est-à-dire sans optimisation. Dés que les optimisations sont activées, à partir de -O1, tous ces codes produisent (naturellement ?) l’assembleur suivant :

main:
mov eax, 560
ret

Horloge Nixie !

En fait, mon horloge Nixie, sujet de tant d’articles sur ce blog depuis un plus d’un an, est terminée depuis un moment ! C’est juste que je n’ai pas posté de l’été alors je n’ai pas eu l’occasion de vous la montrer. Enfin…. seule la partie électronique est terminée : il faut encore lui faire une boite. Voici quelques photos !

Étape 1 : réalisation des PCB

J’ai déjà expliqué comment faire sa carte maison, mais contrairement à la fois précédente, la gravure a été faite au perchlorure de fer. En effet, la carte principale était trop grande et on n’avait pas de bain-marie assez grand pour le faire avec le joli acide bleu. La petite carte a en profité pour faire trempette dans le même bain. Un grand bravo à mon ami Brice pour la mise en place super précise des calques double-face : du beau travail !

nixie_1nixie_2nixie_3

Étape 2 : réunir tous les composants

Je vous passe l’étape fastidieuse du perçage des dizaines de petits trous… Un travail hypnotique largement réalisé au falab de Nantes, le Platforme C.

La plupart des composants sont très simples à trouver et, comme d’habitude, je suis allé chez e44 à Nantes. Les tubes Nixie avaient été achetés il y a très longtemps sur E-Bay et vous pouvez encore en trouver.

nixie_4nixie_5nixie_6nixie_7

Étape 3 : soudage

Ah ! Les douces vapeurs d’étain 🙂 Heureusement, c’était en juin, c’était l’été, on pouvait ouvrir la fenêtre pour ventiler.

Comme les étapes précédente, c’était long, la carte est grande. Ah oui : j’ai fait la carte générant le signal 1 Hz en même temps mais elle a été rapidement expédié et j’ai oublié de la photographier.

La seule vraie difficulté était les tubes… Déjà parce qu’ils sont compliqués à insérer, les pattes sont nombreuses, c’est galère de toutes les faire rentrer en même temps dans le bon ordre. Heureusement, j’avais retenu une technique de fouine lue il y a très longtemps sur le net : couper les pattes pour faire une sorte d’escalier. Très efficace mais attention à ne pas couper trop court ! Il faut prévoir l’épaisseurs de la carte et du porte-tube ! Ensuite, il a fallu ruser fallu ruser pour stabiliser le tout lors de la soudure. Dernier point, il y a une piste sur la face composant qui arrive sous chaque tube… Sous le tube… Galère à souder. La technique est de mettre un peu d’étain, de faire chauffer et d’appuyer pour enfoncer le tube pendant que l’étain est encore liquide ! Ou sinon, faire un strap sous la carte pour « remplacer » la piste. Ça marche aussi et c’est plus simple ^^

nixie_8nixie_9nixie_10nixie_11nixie_12

Étape 4 : it’s alive!

J’avais confiance mais je suis d’un naturel pessimiste… J’ai connecté les cartes entre elles, j’ai branché au secteur… et la lumière fût ! Et avec elle une superbe horloge \o/

Tout ou presque a fonctionné directement. J’ai fait une modification sur la carte car le passage de 23:59 à 00:00 ne se faisait pas… On passait à 24:00, puis à 24:59 on revenait à 05:00. Une piste coupée, un fil et une diode soudés et c’était réparé. Il reste un défaut avec les boutons pour incrémenter les minutes et les herues. Si cela marchait très bien sur ma plaquette d’essai, ce n’est pas le cas ici. L’anti-rebond fonctionne bien mais le bouton devient inopérant pour quelques secondes. Sans doute que la capacité ne se décharge pas mais je n’ai pas encore eu la motivation de brancher l’oscilloscope et de regarder comment corriger ce défaut.

nixie_13nixie_14nixie_15

Conclusion

Ainsi s’achève ce long périple électronique ! Il est possible que je fasse encore un ou deux articles à propos de cet électronique mais je pense que c’est la fin de la série 🙂

Pour ceux que ça intéresse, les schematics et routages sont sur Github (à ouvrir avec Eagle). C’est sous licence open-hardware CERN donc faites vous plaisir !

Maintenant, la boite de l’horloge et trouver de nouveaux projets pour geeker à la maison !

Vérification de types à l’édition des liens

Avec des collègues, on a discuté des vérifications de types à l’édition des liens. Qu’est ce que l’éditeur de liens (le linker) est capable de vérifier ? Je soutenais qu’il ne vérifiait rien alors qu’un collègue soutenait qu’il vérifiait les prototypes des fonctions. J’ai donc souhaité en savoir un peu plus sur le sujet. On parlait de notre expérience en C, mais je m’intéresse aussi au C++ dans cet article.

En première réponse, on peut dire qu’on avait tous les deux tort et raison. En fait, comme (trop) souvent en C (et sans doute en C++, mais je manque d’expérience avec ce langage), chaque toolchain fait ce que bon lui semble. Beaucoup ne vérifient rien, comme le montreront les exemples suivants, l’édition des liens n’étant que la mise en correspondance de symboles qui sont de simples chaines de caractères ; d’autres peuvent utiliser des techniques plus ou moins évolués pour s’assurer de la cohérence des liens.

Ce n’est pas le compilateur qui vérifie le typage ?

Oui, c’est le rôle du compilateur. Pour chaque unité de compilation, il s’assure que l’utilisation des différents éléments (variable, structures, fonctions, etc.) est bien conforme à leurs déclarations. Si vous déclarer une variable de type pointeur, il émettra un warning si vous essayer de vous en servir comme d’un entier. C’est le cas avec le code suivant :

int f(void)
{
    void* p = &main; 
    return p;
}

gcc émet le warning suivant :

main.c: In function ‘f’:
 main.c:24:2: warning: return makes integer from pointer without a cast [enabled by default]
 return p:
 ^
 main.c:24:10: error: expected ‘;’ before ‘:’ token
 return p:
 ^

Si le compilateur fait le boulot, pourquoi se préoccuper du linker ?

Comme précisé ci-dessus, le compilateur fait ce travail pour chaque unité de compilation, mais il ne fait aucun vérification entre unités. C’est le travail du linker que d’assembler les différentes unités compilées. Si une unité de compilation (aka un fichier C) fait référence à une fonction qui est dans une autre unité grâce à une déclaration avec le mot-clé extern, qui vérifie que cette déclarée est conforme à la définition de la fonction ? Le linker est le dernier rempart pour ne pas associer deux symboles qui ne sont pas utilisés de la même manière dans deux unités de compilation.

Pour tester cela, j’ai écrit deux fichiers C. Le premier, code.c contient des variables globales (c’est mal, c’est juste pour l’exemple) et des fonctions :

const float PI = 3.14;
const int magic = 42;

int function_char(signed char n)
{
    return n * 2;
}

double function_doubles(double a, double b)
{
    return a + b;
}

Le second, main.c les utilise :

#include <stdio.h>

extern int function_char(unsigned char a);
extern double function_doubles(float a, float b);
extern int PI(void);
extern int magic(void);

int main(void)
{
	printf("function_char(200) = %d\n", function_char(200));
	
	printf("function_doubles(3.14 + 2.42) = %f\n", function_doubles(3.14, 2.42));
	
	printf("PI as function: %d\n", PI());
	
	printf("magic as function: %d\n", magic());
}

Toutes les déclarations externes sont fausses. Les fonctions n’ont pas les bons prototypes et, beaucoup plus grave, les variables sont ici utilisées comme des fonctions. Ça ne va quand même pas passer !? Désolé de vous décevoir mais gcc vous génère un exécutable sans ciller :

$ gcc -Wall -Wextra -std=c99 main.c code.c && ./a.out 
function_char(200) = -112
function_doubles(3.14 + 2.42) = 6.720002
PI as function: 41
Segmentation fault (core dumped)

Il n’y a donc aucune vérification lors de l’édition des liens…

  • 200, qui tient normalement très bien dans un unsigned char, est finalement interprété comme un signed char, ce qui donne un nombre négatif. On obtient donc -112 au lieu de 400.
  • On pourrait penser que les floats passeraient très bien en devenant des doubles, raté.
  • Exécuter PI donne un résultat ! On se demande un peu ce qu’il est allé exécuter…
  • Exécuter magic provoque une segmentation fault. Je dirai « heureusement ! » car ça nous permet de se rendre compte d’un problème.

Comment se protéger de tels problèmes ?

Faut-il rendre les éditeurs de liens plus sécurisés ? Possible, mais ce n’est pas dans notre périmètre d’actions en tant que développeurs. La meilleure parade est de ne jamais utiliser de déclarations externes dans un fichier source. Celles-ci devraient toujours être dans un fichier d’en-tête, qui serait inclus dans le fichier source où l’on souhaite utiliser les éléments ainsi que dans le fichier source où ils sont définis. Ainsi, le compilateur pourra vérifier que la déclaration donnée dans le fichier d’en-tête est conforme avec l’utilisation et avec la définition.

En reprenant mon exemple précédent, j’ai écrit le fichier d’en-tête suivant code.h :

extern const float PI;
extern const int magic;
int function_char(signed char n);
double function_doubles(double a, double b);

Je l’ai inclus dans main.c (en supprimant les déclarations externes) et dans code.c et j’ai récompilé :

$ gcc -Wall -Wextra -std=c99 main.c code.c && ./a.out 
main.c: In function ‘main’:
main.c:10:35: error: called object ‘PI’ is not a function or function pointer
  printf("PI as function: %d\n", PI());
                                   ^
In file included from main.c:2:0:
code.h:1:20: note: declared here
 extern const float PI;
                    ^
main.c:12:41: error: called object ‘magic’ is not a function or function pointer
  printf("magic as function: %d\n", magic());
                                         ^
In file included from main.c:2:0:
code.h:2:18: note: declared here
 extern const int magic;
                  ^

En supprimant les appels à PI et magic, cela compile sans warning et donne le résultat suivant :

function_char(200) = -112
function_doubles(3.14 + 2.42) = 5.560000

Le calcul avec des flottants fonctionne correctement. On a toujours notre overflow pour 200 et le compilateur ne nous dit rien car il y a probablement une règle de conversion définie. C’est quand même beaucoup mieux !

Notez au passage que cela n’est possible que si vous recompiler tout. Si au lieu de code.c, vous avez code.o ou code.a accompagné de code.h, il ne vous reste plus qu’à souhaiter que le fichier d’en-tête est cohérent avec le contenu de la bibliothèque. Cela m’est déjà arrivé avec des définitions de structures par exemple.

Un pas de plus dans les profondeurs de l’édition des liens

Pour comprendre un peu mieux pourquoi l’éditeur de liens ne peut pas vérifier les types, il faut regarder un peu plus en détails les résultats de la compilation, les fichiers objets *.o. Ici, je suis revenu au code de départ, avant l’utilisation de code.h, et j’ai modifié un peu ma ligne de commande pour obtenir les fichiers objets :

$ gcc -Wall -Wextra -std=c99 main.c code.c -c

Je peux maintenant regarder la table des symboles de deux fichiers objets :

$ objdump -t code.o main.o 

code.o:     file format elf32-i386

SYMBOL TABLE:
00000000 l    df *ABS*    00000000 code.c
00000000 l    d  .text    00000000 .text
00000000 l    d  .data    00000000 .data
00000000 l    d  .bss    00000000 .bss
00000000 l    d  .rodata    00000000 .rodata
00000000 l    d  .note.GNU-stack    00000000 .note.GNU-stack
00000000 l    d  .eh_frame    00000000 .eh_frame
00000000 l    d  .comment    00000000 .comment
00000000 g     O .rodata    00000004 PI
00000004 g     O .rodata    00000004 magic
00000000 g     F .text    00000014 function_char
00000014 g     F .text    0000002e function_doubles

main.o:     file format elf32-i386

SYMBOL TABLE:
00000000 l    df *ABS*    00000000 main.c
00000000 l    d  .text    00000000 .text
00000000 l    d  .data    00000000 .data
00000000 l    d  .bss    00000000 .bss
00000000 l    d  .rodata    00000000 .rodata
00000000 l    d  .note.GNU-stack    00000000 .note.GNU-stack
00000000 l    d  .eh_frame    00000000 .eh_frame
00000000 l    d  .comment    00000000 .comment
00000000 g     F .text    0000007c main
00000000         *UND*    00000000 function_char
00000000         *UND*    00000000 printf
00000000         *UND*    00000000 function_doubles
00000000         *UND*    00000000 PI
00000000         *UND*    00000000 magic

Dans code.o, on voit bien nos variables et nos constantes. Il y a leurs noms et on peut savoir si ce sont des variables ou des fonctions car les premières sont dans la section .rodata (pour Read-Only Data, car ce sont des constants ; elles auraient été dans .data sans le qualificatif const) et les secondes dans la section .text. Dans main.o, ces symboles sont marqués comme *UND* et n’ont pas d’adresse. C’est le principe même de l’édition des liens que de leur donner une adresse en cherchant le symbole de même nom dans les autres fichiers objets. On constate bien que le compilateur ne laisse aucune information dans main.o pour permettre au linker pour vérifier les types.

Que se passe t-il en C++ ?

C’est presque radicalement différent !

$ g++ -Wall -Wextra -std=c++11 main.c code.c && ./a.out 
/tmp/cc7UfAzd.o: In function `main':
main.c:(.text+0x11): undefined reference to `function_char(unsigned char)'
main.c:(.text+0x37): undefined reference to `function_doubles(float, float)'
main.c:(.text+0x4c): undefined reference to `PI()'
main.c:(.text+0x61): undefined reference to `magic()'
collect2: error: ld returned 1 exit status

C’est génial ! Le linker vérifie les types ! Euh… non. Regardons les tables des symboles :

$ g++ -Wall -Wextra -std=c++11 main.c code.c -c
$ objdump -t code.o main.o 
code.o:     file format elf32-i386

SYMBOL TABLE:
00000000 l    df *ABS*    00000000 code.c
00000000 l    d  .text    00000000 .text
00000000 l    d  .data    00000000 .data
00000000 l    d  .bss    00000000 .bss
00000000 l    d  .rodata    00000000 .rodata
00000000 l    d  .note.GNU-stack    00000000 .note.GNU-stack
00000000 l    d  .eh_frame    00000000 .eh_frame
00000000 l    d  .comment    00000000 .comment
00000000 g     O .rodata    00000004 PI
00000004 g     O .rodata    00000004 magic
00000000 g     F .text    00000014 _Z13function_chara
00000014 g     F .text    00000026 _Z16function_doublesdd

main.o:     file format elf32-i386

SYMBOL TABLE:
00000000 l    df *ABS*    00000000 main.c
00000000 l    d  .text    00000000 .text
00000000 l    d  .data    00000000 .data
00000000 l    d  .bss    00000000 .bss
00000000 l    d  .rodata    00000000 .rodata
00000000 l    d  .note.GNU-stack    00000000 .note.GNU-stack
00000000 l    d  .eh_frame    00000000 .eh_frame
00000000 l    d  .comment    00000000 .comment
00000000 g     F .text    0000007c main
00000000         *UND*    00000000 _Z13function_charh
00000000         *UND*    00000000 printf
00000000         *UND*    00000000 _Z16function_doublesff
00000000         *UND*    00000000 _Z2PIv
00000000         *UND*    00000000 _Z5magicv

WTF?! Que sont ces noms ésotériques ?

C++ autorise la surcharge de fonctions, c’est-à-dire que deux fonctions peuvent avoir le même nom tout en ayant des prototypes différents. Comment faire alors pour différencier void f(int) de void f(int, int) ou encore de void f(void) ? Le compilateur utilise la technique du name mangling : il créé le symbole à partir du nom de la fonction tout en rajoutant des caractères plus ou moins cryptiques pour distinguer les prototypes. C’est ce que nous montre les tables ci-dessus. Les symboles sont différents entre les deux fichiers objets car les déclarations sont différentes entre les deux fichiers sources. Lors de l’édition des liens, il n’y a pas plus de vérification de types qu’en C (c’est d’ailleurs toujours ld qui fait ce boulot), mais comme les noms de symboles diffèrent selon le typage, il n’est pas possible de lier function_char utilisée dans main.c avec celle définie dans code.c car elles conduisent à des symboles différents.

Au passage, il n’est pas légal de surcharger une fonction en changeant son type de retour ; le name mangling ne « protège » donc que des incohérences de paramètres.

Le fameux extern "C" sert à préciser au compilateur de pas utiliser de name mangling pour générer le symbole correspondant à une fonction. Cela sert quand on utilise cette fonction dans fichier compilé en C++ alors que sa définition est dans un fichier compilé en C.

Conclusion et quelques liens

En conclusion, on voit qu’il faut se méfier des incohérences de typage entre plusieurs unité de compilation. C’est valable pour les fonctions, les variables, les constants ou encore les définitions de nouveaux types comme les structures. De manière accidentelle, le C++ est moins sensible que le C à cause de la surcharge de fonctions et de la technique du name mangling. Les normes C et C++ ne définissant rien à ce sujet, chaque toolchain pourra implémenter diverses techniques pour éviter des erreurs de type à l’édition des liens. On pourrait imaginer du name mangling en C même sans surcharge. De manière générale, la meilleure solution est celle déjà évoquée de bannir le mot-clé extern des fichiers *.c et de l’utiliser uniquement dans les fichiers *.h, mais de toute façon, vous utilisez déjà toujours des fichiers d’en-tête pour accéder aux API des autres modules, non ?

Some Differences Between C and C++ par Steve Jacobson
Voir les deux dernières sections Name Mangling et Linking C and C++ files (or Libraries)

Beginner’s Guide to Linkers

Is there any type checking in C or C++ linkers? sur stackoverflow

Checking C Declarations at Link Time par Diomidis Spinellis

Créer son propre archétype Maven

Aujourd’hui, j’ai enfin pris le temps de voir comment créer son propre archétype Maven. L’idée m’était venue en octobre, notamment avec la lecture de cet article, Maven pour les nuls… les archetypes. Avoir son propre archétype permet de créer un nouveau projet en respectant une structure donnée très simplement. Il est ainsi possible d’uniformiser les développements au sein de l’entreprise en fournissant un modèle qui sera commun à tous les projets. Pour cette expérimentation, j’ai choisi de créer un modèle de projet avec une structure ressemblant à celle que pourrait avoir un projet en C sur MCU et dont l’artéfact généré serait un zip contenant l’intégralité du projet. Je travaille dans Eclipse Mars pour plus de faciliter mais vous pourriez très bien faire la même chose avec un éditeur de texte et la ligne de commande.

Création du modèle de projet

La première étape est de créer un projet tel que j’aimerais que Maven le génère avec mon archétype. Une fois cette coquille créée, je demanderai à Maven de générer l’archétype à partir de ce modèle (template, en anglais). Voici à quoi ressemble mon projet :

maven - archetype - 1 - template

La seconde étape est la personnalisation du fichier pom.xml pour générer non pas un jar mais un zip du projet. Pour cela, il faut utiliser le plugin Assembly de Maven. En suivant les conseils de la section Usage, j’ai rajouté l’utilisation de ce plugin, j’ai choisi le mode project et j’ai demandé à ce que la cible qui va bien d’Assembly soit appelée lors de la phase package du projet. Enfin, j’ai changé le type de packaging du projet de jar vers pom pour ne plus générer de jar mais uniquement le zip d’Assembly. Voici le pom.xml :

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.gradot</groupId>
    <artifactId>mcu-project-template</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>2.6</version>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>project</descriptorRef>
                    </descriptorRefs>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

Création du projet Maven de l’archétype

À ce state, je possède donc un modèle de projet avec la structure et le packaging souhaités. En exécutant mvn package sur le projet, on constate en effet que le dossier de sortie target contient bien 3 archives (zip, tar.gz, targ.bz2). Il faut donc maintenant créer le projet Maven de l’archétype en lui-même, à partir de ce projet template, grâce à la commande mvn archetype:create-from-project. J’ai essayé de le faire directement avec un launch configuration d’Eclipse mais j’ai obtenu l’erreur suivante :

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-archetype-plugin:2.4:
create-from-project (default-cli) on project mcu-project-template: 
${maven.home} is not specified as a directory: 
'/home/pgradot/Documents/workspace-mars/mcu-project-template/EMBEDDED'. -> [Help 1]

Cela semble être dû à un bug de l’intégration de Maven dans Eclipse. La solution de contournement est simple :  exécuter la commande via le terminal. Le projet Maven de l’archétype est alors disponible dans target/generated-sources/archetype. Pour plus de facilité pour la suite, j’ai créé un projet Eclipse en utilisant une non-default location pointant vers l’emplacement du projet Maven de l’archétype :

maven - archetype - 2 - create eclipse project

Enfin, j’ai rajouté la nature Maven au projet Eclipse en faisant un clic-droit puis Configure / Convert to Maven project. On peut maintenant regarder le contenu de l’archétype et notamment retrouver les fichiers de notre projet template :maven - archetype - 3 - archetype content

Hic ! Les dossiers vides ne sont pas gardés lors de la génération de l’archétype…

Publication et utilisation de ce nouvel archétype

Pour publier l’archétype et ainsi le rendre disponible, il suffit d’appeler la cible install du projet (ou deploy, si les dépôts sont configurés) et l’artéfact de l’archétype se retrouve dans le dépôt local de Maven :

$ ls -l /home/pgradot/.m2/repository/com/gradot/mcu-project-template-archetype
/1.0.0-SNAPSHOT/
total 20
-rw-rw-r-- 1 pgradot pgradot   96 mai   10 08:34 m2e-lastUpdated.properties
-rw-rw-r-- 1 pgradot pgradot  726 mai   10 08:30 maven-metadata-local.xml
-rw-rw-r-- 1 pgradot pgradot 3241 mai   10 08:30 mcu-project-template-archetype
-1.0.0-SNAPSHOT.jar
-rw-rw-r-- 1 pgradot pgradot  988 mai   10 08:24 mcu-project-template-archetype
-1.0.0-SNAPSHOT.pom
-rw-rw-r-- 1 pgradot pgradot  238 mai   10 08:30 _remote.repositories

Je peux maintenant créer un nouveau projet dans Eclipse et demander à Maven d’utiliser mon nouvel archétype (il faut penser à cocher qui va bien la case si l’archétype a été buildé en snapshot, ce qui est le cas ici) :maven - archetype - 4 - create projet

Pour finir je peux constater que j’obtiens un projet semblable à mon projet template (aux dossier vides près… ) et qui se package comme il faut !

maven - archetype - 5 - result