Nouveau

Empêcher l’ouverture d’une pop-up quand un programme Windows plante

Par défaut, quand votre programme C ou C++ plante sous Windows, une pop-up s’ouvre pour vous l’annoncer. Ça donne quelque chose comme ceci (avec mingw-w64 dans Eclipse) :

C’est sympa, mais ça peut poser des problèmes. En particulier, cela implique que votre programme continue de s’exécuter jusqu’à ce que quelqu’un clique sur le bouton « Fermer le programme« . Et ce quelqu’un n’arrivera peut-être jamais… C’est notamment le cas quand votre application crashe sur votre serveur d’intégration continue. L’application ne fait rien mais ne s’arrête pas sans que l’outil d’intégration continue ne puisse faire quoique ce soit (en fait, il ne le sait même pas), à part finir par faire un abort de la construction quand le timeout aura été atteint (si vous en avez configurer un…).

La solution est très simple, il suffit de rajouter une petite ligne au début de votre main() (en ayant bien sûr inclus le bon fichier d’en-tête) :

#include  <windows.h>

int main() {
    SetErrorMode (SEM_FAILCRITICALERRORS |
          SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX);

    // Votre programme

Votre programme s’arrêtera et le code de retour vous montera que le programme a planté :

Ça fonctionne aussi quand une exception n’est pas catchée ou quand votre programme rencontre un assert() qui échoue !

Pour plus de détails sur la fonction SetErrorMode():

  • sa documentation sur MSDN (vous y lirez ce magnifique commentaire : « Best practice is that all applications call the process-wide SetErrorMode function with a parameter of SEM_FAILCRITICALERRORS at startup. This is to prevent error mode dialogs from hanging the application. » =)
  • son implémentation dans mingw64

FreeRTOS Thread Debugging with Eclipse and OpenOCD

Si vous utilisez FreeRTOS et que votre IDE est Eclipse et que vous vous connectez à votre carte grâce à OpenOCD, ceci devrait vous intéresser !

Avatar de Erich StygerMCU on Eclipse

FreeRTOS is probably the number one RTOS used, and Eclipse is likely the most popular IDE I can think of. But debugging FreeRTOS applications with Eclipse and GDB is somewhat limited? What I would like to get at the minimum is this: ability to see all the different threads in the Eclipse debug view like this:

FreeRTOS Threads in Eclipse with OpenOCD FreeRTOS Threads in Eclipse with OpenOCD

As you might guess from that screenshot: this post is about how to make FreeRTOS tread debugging possible with Eclipse and GDB :-).

Voir l’article original 594 mots de plus

Formater du texte en C++ avec {fmt}

Formater du texte en C++, c’est pas ultra fun… Il y a bien les « bons » vieux reliquats du C avec les fonctions genre std::printf() et les méthodes un peu plus modernes comme std::ostringstream. C’est pas génial comparer à string.format() de Python En regardant le code de spdlog( une bibliothèque de logging qui semble très bien mais qui malheureusement n’était pas compatible avec mes contraintes), j’ai découvert la bibliothèque {fmt}. C’est elle qui sert de back-end à spdlog pour formater les messages. Si vous voulez essayer {fmt}, voici le mode opératoire !

Builder la bibliothèque

Tout le code est sur GitHub, est placé sous licence BSD et se builde avec CMake. Le mode opératoire est donc très classique :

$ git clone https://github.com/fmtlib/fmt.git
$ cd fmt
p$ mkdir build
$ cd build
$ cmake ..
[...]
$ make all
Scanning dependencies of target fmt
[  2%] Building CXX object CMakeFiles/fmt.dir/src/format.cc.o
[  4%] Building CXX object CMakeFiles/fmt.dir/src/posix.cc.o
[  7%] Linking CXX static library libfmt.a
[  7%] Built target fmt
Scanning dependencies of target gmock
[  9%] Building CXX object test/CMakeFiles/gmock.dir/gmock-gtest-all.cc.o
[ 12%] Linking CXX static library libgmock.a
[ 12%] Built target gmock
Scanning dependencies of target test-main
[ 14%] Building CXX object test/CMakeFiles/test-main.dir/test-main.cc.o
[ 17%] Building CXX object test/CMakeFiles/test-main.dir/gtest-extra.cc.o
[ 19%] Building CXX object test/CMakeFiles/test-main.dir/util.cc.o
[ 21%] Linking CXX static library libtest-main.a
[ 21%] Built target test-main
Scanning dependencies of target time-test
[ 24%] Building CXX object test/CMakeFiles/time-test.dir/time-test.cc.o
[ 26%] Linking CXX executable ../bin/time-test
[...]
[100%] Linking CXX executable ../bin/util-test
[100%] Built target util-test

$ make test
Running tests...
Test project /home/pgradot/Documents/GitHub/fmt/build
      Start  1: assert-test
 1/11 Test  #1: assert-test ......................   Passed    0.02 sec
      Start  2: gtest-extra-test
 2/11 Test  #2: gtest-extra-test .................   Passed    0.03 sec
      Start  3: format-test
[...]
      Start 10: posix-mock-test
10/11 Test #10: posix-mock-test ..................   Passed    0.20 sec
      Start 11: posix-test
11/11 Test #11: posix-test .......................   Passed    4.85 sec

100% tests passed, 0 tests failed out of 11

Total Test time (real) =   5.84 sec


$ sudo make install
[  7%] Built target fmt
[...]
[100%] Built target format-impl-test
Install the project...
-- Install configuration: "Release"
-- Installing: /usr/local/lib/cmake/fmt/fmt-config.cmake
-- Installing: /usr/local/lib/cmake/fmt/fmt-config-version.cmake
-- Installing: /usr/local/lib/cmake/fmt/fmt-targets.cmake
-- Installing: /usr/local/lib/cmake/fmt/fmt-targets-release.cmake
-- Installing: /usr/local/lib/libfmt.a
-- Installing: /usr/local/include/fmt/core.h
-- Installing: /usr/local/include/fmt/format.h
-- Installing: /usr/local/include/fmt/format-inl.h
-- Installing: /usr/local/include/fmt/locale.h
-- Installing: /usr/local/include/fmt/ostream.h
-- Installing: /usr/local/include/fmt/printf.h
-- Installing: /usr/local/include/fmt/time.h
-- Installing: /usr/local/include/fmt/posix.h

J’ai volontairement raccourci la sortie de plusieurs commandes (vous avez sans doute vu les […]). Vous n’êtes pas obligé de faire make test mais c’est sympa de vérifier que notre bibliothèque s’est correctement compilée. On voit que l’installation a copié dans des dossiers classiques la bibliothèque statique libfmt.a ainsi que les nombreux headers dont nous aurons besoin pour l’utiliser dans notre code. Vous pourriez ne pas l’installer et récupérer les fichiers pour les mettre dans votre projet ou encore utiliser directement le CMake de {fmt} comme un sous-CMake de votre projet.

Petits essais

J’ai repris quelques lignes données dans le README du GitHub et j’ai écrit une petite fonction log() puisque c’est un peu pour ça que je me suis intéressé à {fmt} :

#include 
#include 
#include 

namespace pgt
{
template 
void log(const char* format, Args&& ... args)
{
	try
	{
		auto now = std::time(nullptr);
		auto timestamp = std::string(std::ctime(&now));
		std::replace(timestamp.begin(), timestamp.end(), '\n', '\0');

		auto message = fmt::format(format, args...);
		fmt::print("[{}] {}\n", timestamp, message);
	}
	catch(const fmt::format_error& e)
	{
		fmt::print("Invalid log message: {}\n", e.what());
	}
}
}

int main()
{
	// Exemples de GitHub
	fmt::print("Hello, {}!\n", "world");
	fmt::printf("Hello, %s!\n", "world");
	std::string s = fmt::format("{0}{1}{0}\n", "abra", "cad");
	fmt::print(s);

	// Log
	int speed = 1200;
	pgt::log("Speed = {} rpm - Temperature = {}°C", speed, 42);
	pgt::log("Setpoint = {}");
}

On compile et on lance le programme :

$ g++ -Wall -Wextra -std=c++11 main.cpp -lfmt && ./a.out
Hello, world!
Hello, world!
abracadabra
[Fri May 11 11:59:13 2018] Speed = 1200 rpm - Temperature = 42°C
Invalid log message: argument index out of range

Notes :

  1. C’est pas une super bonne idée de faire une fonction qui s’appelle log dans le namespace global, elle risque d’entrer en conflit avec std::log, qui dans mon cas était disponible (le test a été fait avec un gcc 5.4). Ainsi, un appel à log(42) compilait sans erreur alors que pgt::log(42) génère bien un message d’erreur.
  2. Il y a moyen de faire encore mieux pour formater le timestamp des logs mais mon vieux compilateur semblait perdu…
  3. Si on intervertit main.cpp et -lfmt dans la ligne de commande, on se mange une palanquée d’erreurs de link.
  4. Oui, un formatage peut rater et lancer des exceptions !

Personnellement, je trouve ça bien sympa !

Pour aller plus loin, vous pouvez regarder la documentation de l’API ou approfondir sur la syntaxe du formatage,

C’est tout pour aujourd’hui 🙂

C++17 et fold expression

Il y a quelques jours, j’ai découvert les fold expressions, une nouveauté de C++17. J’ai trouvé ça cool. Je me suis surtout demandé à quoi ça pourrait me servir. Et puis j’ai pensé à un code écrit il y a quelques semaines et et je me suis dit que ça aurait sans doute été plus simple avec cette fonctionnalité. Je ne vais pas modifier le vrai code puisqu’il marche et est déjà utilisé dans un logiciel professionnel mais j’ai fait un code d’essai pour voir. Et effectivement, c’est bien mieux ! Je vais vous montrer ça.

Le but est de monitorer des variables pendant l’exécution du programme. J’ai pour cela une classe Monitor à laquelle on peut ajouter des variables à surveiller. Voici à quoi ressemble le code :

#include <iostream>

class Monitor {
public:
    void watch(short& s [[maybe_unused]]) {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }

    void watch(int& i [[maybe_unused]]) {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }

    void watch(long& l [[maybe_unused]]) {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }
};

int main(void) {
    Monitor m;

    short s = 0;
    m.watch(s);

    int i = 0;
    m.watch(i);

    long l = 42;
    m.watch(l);
}

J’en profite au passage pour vous montrer l’extensions GNU __PRETTY_FUNCTION__ qui permet d’afficher joliment le nom de la fonction et une utilisation de l’attribut maybe_unused (une autre nouveauté de C++17) pour à éviter le classique (void) paramètre_qui_ne_sert_pas dans le corps des fonctions. On a ainsi un code qui compile sans warning et qui produit la sortie suivante :

void Monitor::watch(short int&)
void Monitor::watch(int&)
void Monitor::watch(long int&)

À l’utilisation, il s’avère qu’il est pratique de pouvoir ajouter plusieurs variables d’un coup, par exemple en écrivant m.watch(s, i, l);. L’idée est de créer une fonction template prenant un nombre variable de paramètres, chacun pouvant être d’un type différent des autres. C++11 a apporté cette possibilité, grâce aux variadic templates et aux parameter packsUne telle fonction ressemblerait à ça :

template<typename...Targs>
void function(Targs... args) {
}

Ça se corse quand on souhaite itérer sur les éléments du pack… La technique classique consiste à fait faire des appels récursifs à la fonction pour réduire le pack. Pour la classe Monitor, il convient donc de rajouter deux fonctions :

class Monitor {
public:
    // idem

    template<typename T, typename...Targs>
    void watchSeveral_recursive(T& t, Targs&...args) {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
        watch(t);
        watchSeveral_recursive(args...);
    }

private:
    template<typename T>
    void watchSeveral_recursive(T& t) {
        std::cout << __PRETTY_FUNCTION__ << " (final) " << std::endl;
        watch(t);
    }
};

La fonction publique est effectivement récursive. L’astuce est d’avoir un paramètre simple en plus du pack. Chaque appel imbriqué va ainsi « réduire le pack », puisque le premier élément du pack va devenir le premier argument et que le reste du pack va créer un nouveau pack en deuxième argument. Comme toute récursion, il faut un cas d’arrêt : c’est le rôle de la fonction privée. Elle est appelée quand le pack ne contient plus qu’un élément puisque le compilateur va préférer cette surcharge privée à la surcharge publique dans un tel cas. Ça donne ça à l’utilisation :

int main(void) {
    Monitor m;

    short s = 0;
    int i = 0;
    long l = 42;

    m.watchSeveral_recursive(s, i, l);
}
void Monitor::watchSeveral_recursive(T&, Targs& ...)
    [with T = short int; Targs = {int, long int}]
void Monitor::watch(short int&)
void Monitor::watchSeveral_recursive(T&, Targs& ...)
    [with T = int; Targs = {long int}]
void Monitor::watch(int&)
void Monitor::watchSeveral_recursive(T&)
    [with T = long int] (final) 
void Monitor::watch(long int&)

Sympa, hein ?

Voici maintenant la version avec une fold expression :

class Monitor {
public:
    // idem

    template<typename...Ts>
    void watchSeveral_withFoldExpr(Ts&...args) {
        std::cout <<__PRETTY_FUNCTION__ << std::endl;
        (watch(args), ...);
        // Notez l'utilisation de parentheses !
    }
};

Elle s’utilise évidemment de la même manière que précédemment :

int main(void) {
    Monitor m;

    short s = 0;
    int i = 0;
    long l = 42;

    m.watchSeveral_withFoldExpr(s, i, l);
}

La sortie est bien sûr ce qu’on s’attend à obtenir :

void Monitor::watchSeveral_withFoldExpr(Ts& ...)
    [with Ts = {short int, int, long int}]
void Monitor::watch(short int&)
void Monitor::watch(int&)
void Monitor::watch(long int&)

Voilà, voilà… J’avais dit que c’était mieux ^^ C’est tellement simple que je n’ai pas besoin de vous expliquer le code. Il suffit que de savoir que c’est une fold expression pour le comprendre. C’est une fonctionnalité toute nouvelle, peu de gens la connaissent, mais vous oui après la lecture de cet article 🙂

Laisse faire ton compilateur, il sait ce qu’il fait :)

Ce qui est bien avec les compilateurs modernes, c’est qu’ils sont très bons pour optimiser le code à notre place. Un bon code est un code efficace mais simple, que le compilateur comprendra et analysera facilement. Le développeur pourra décider d’écrire un code « plus optimisé » si et seulement si le compilateur n’a pas réussi à produire quelque chose de satisfaisant. Mais il devrait quand même d’abord essayer de clarifier son code 😉

J’en ai encore fait l’expérience ces jours-ci en me demandant si un ranged-loop for (apparu en C++11) sur un tableau avait un overhead par rapport à un parcours classique avec un index de 0 à taille-1. Je suis donc aller sur Compiler Explorer et j’ai écrit ce petit code :

void f(int v);

constexpr int SIZE = 128;
int array[SIZE];

void g() {
    for(auto& e : array) {
        f(e);
    }
}

void h() {
    for(int i = 0; i < SIZE; ++i) {
        auto& e = array[i];
        f(e);
    }
}

Je l'ai ensuite compilé avec GCC 7.2.1 pour ARM, puisque c'est la toolchain que j’utilise sur mon projet actuel. Avec l’optimisation à O2 ou O3, les codes assembleurs générés sont les mêmes. En revanche, une des versions est un poil meilleure en O1…. et ce n’est pas la version classique mais la version mode du ranged-loop for. Sans doute parce qu’il n’y a aucune ambiguïté sur le code : il s’agit bien de parcourir le tableau élément par élément. La différence est minime toutefois 😉

Voici ce que ça donne (les couleurs montrent la correspondance code source / code assembleur) :

 

Conclusion : laissez faire votre compilateur, il sait ce qu’il fait…. et sans doute bien mieux que vous !

Concevoir un site comme celui-ci avec WordPress.com
Commencer