IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage C++ Discussion :

Différence entre #define et static constexpr


Sujet :

Langage C++

  1. #1
    Membre éclairé Avatar de pcdwarf
    Homme Profil pro
    Administrateur systèmes et réseaux
    Inscrit en
    Février 2010
    Messages
    269
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : Administrateur systèmes et réseaux
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Février 2010
    Messages : 269
    Par défaut Différence entre #define et static constexpr
    Bonjour,

    J'écris un driver pour un micro-contrôleur doté de très peu de RAM et je suis donc très préoccupé par le non-gaspillage de mémoire.
    Le driver est sous la forme d'une classe.

    À l'intérieur, j'ai un grand nombre d'étiquettes pour nommer des valeurs comme par exemple des adresses de registres et des numéros de bits.

    Je peux utiliser le préprocesseur avec des #define. C'est la méthode classique, mais ça pose le problème de devoir gérer l'unicité des étiquettes. Je peux utiliser des enum mais ça suppose que toutes les étiquettes d'un même enum ont des valeurs différentes, ce qui est parfois une contrainte. Ou bien je peux déclarer des attributs privés en static constexpr

    …sauf que j'ai un doute.

    est-ce que la constexpr est vraimment évaluée à la compilation et remplacée lors des appels comme si on passait une valeur immédiate ?
    Autrement dit, est-ce que ça produit les mêmes opcodes qu'avec un #define ou des valeurs immédiates ?

    Ou bien est-ce que c'est géré comme une variable const, c'est à dire constant mais avec tout de même l'allocation d'une adresse de stockage en RAM ?

    D'après les essais que j'ai fait, il semble que la constexpr soit bien équivalente à une valeur immédiate prise en ROM. Mais ce que j'ignore, c'est si c'est garanti ou pas, ou bien si c'est spécifique à GCC, cette architecture ou bien mes options de compilation

  2. #2
    Expert confirmé
    Homme Profil pro
    Ingénieur développement matériel électronique
    Inscrit en
    Décembre 2015
    Messages
    1 599
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 62
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur développement matériel électronique
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Décembre 2015
    Messages : 1 599
    Par défaut
    Bonjour,

    Oui, c'est garanti. Une variable constexpr est implicitement inline, et toujours résolue à la compilation.

  3. #3
    Membre éclairé Avatar de pcdwarf
    Homme Profil pro
    Administrateur systèmes et réseaux
    Inscrit en
    Février 2010
    Messages
    269
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : Administrateur systèmes et réseaux
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Février 2010
    Messages : 269
    Par défaut
    Merci pour ta réponse ultra-rapide.

  4. #4
    Membre Expert
    Femme Profil pro
    ..
    Inscrit en
    Décembre 2019
    Messages
    667
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Âge : 95
    Localisation : Autre

    Informations professionnelles :
    Activité : ..

    Informations forums :
    Inscription : Décembre 2019
    Messages : 667
    Par défaut
    Salut,

    L'énumération délimitée/fortement typée (enum class) peut constituer une très bonne alternative.
    Et contrairement à ce que tu as écrit, dans une énumération, rien n'interdit aux énumérateurs d'avoir des mêmes valeurs.

  5. #5
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 633
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 633
    Par défaut
    Salut,
    Citation Envoyé par kaitlyn Voir le message
    Salut,

    L'énumération délimitée/fortement typée (enum class) peut constituer une très bonne alternative.
    Et contrairement à ce que tu as écrit, dans une énumération, rien n'interdit aux énumérateurs d'avoir des mêmes valeurs.
    En effet, deux énumérateurs issus d'une même énumération (qu'il s'agisse d'une énumération "classique" ou d'une énumération fortement typée) peuvent parfaitement être associés à des valeurs identiques.

    Par contre, le problème sera systématiquement le même quelle que soit la solution choisie : comme les noms ne sont utilisés que dans le code que l'on écrit et étant "oubliés" dans l'exécutable généré, dés qu'il s'agira de vérifier les valeurs maintenues en mémoire, des "faux positifs", voire des conflits, peuvent parfaitement apparaitre lors des tests.

    Par exemple, un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    #include <iostream>
    #include <vector>
    enum class Color{
        red = 1,
        green = 2,
        blue = 4,
        yellow = 8,
        purple = 4
    };
    int main(){
        Color c{Color::purple};
        if(c == Color::blue)
            std::cout <<"this color is blue";
        else
            std::cout<<"this color isn't purple";
    }
    affichera ... this color is blueEt, de même, un switch ... case prenant la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    #include <iostream>
    #include <vector>
    enum class Color{
        red = 1,
        green = 2,
        blue = 4,
        yellow = 8,
        purple = 4
    };
    int main(){
        Color c{Color::purple};
        std::cout<<"this color is ";
        switch (c){
            case Color::red : 
                std::cout<<"red";
                break;
            case Color::green : 
                std::cout<<"green";
                break;
            case Color::blue : 
                std::cout<<"blue";
                break;
            case Color::yellow : 
                std::cout<<"yellow";
                break;
            case Color::purple : 
                std::cout<<"purple";
                break;
        }
    }
    fera que le compilateur se plaigne d'un conflit pour la raison que
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    <source>:27:14: error: duplicate case value: 'blue' and 'purple' both equal '4'
            case Color::purple : 
                 ^
    <source>:21:14: note: previous case defined here
            case Color::blue :
    Dans tous les cas, il faut donc vraiment s'assurer que tous les identifiants "mis en commun" soient associés à des valeurs numériques différentes.
    Citation Envoyé par pcdwarf Voir le message
    Bonjour,

    J'écris un driver pour un micro-contrôleur doté de très peu de RAM et je suis donc très préoccupé par le non-gaspillage de mémoire.
    Le driver est sous la forme d'une classe.

    À l'intérieur, j'ai un grand nombre d'étiquettes pour nommer des valeurs comme par exemple des adresses de registres et des numéros de bits.

    Je peux utiliser des enum mais ça suppose que toutes les étiquettes d'un même enum ont des valeurs différentes, ce qui est parfois une contrainte.
    Je ne vois personnelle aucune raison qui pourrait justifier d'avoir plusieurs identifiants associés à une même valeur,et donc, si tu as un exemple qui pourrait le justifier, il est le bienvenu .

    De mon point de vue (qui vaut ce qu'il vaut ), les identifiants que l'on crée -- quelle que soit la manière utilisée -- ont uniquement pour but de permettre au lecteur (humain) du code de se faire une idée rapide de l'élément que l'on manipule, et la valeur numérique associée à cet identifiant n'a pour seul objectif de permettre la même chose pour le compilateur

    Dans les deux cas, l'unicité de l'association est absolument indispensable pour éviter les éventuels conflits ou malentendus
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  6. #6
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 144
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 144
    Billets dans le blog
    4
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Je ne vois personnelle aucune raison qui pourrait justifier d'avoir plusieurs identifiants associés à une même valeur,et donc, si tu as un exemple qui pourrait le justifier, il est le bienvenu .
    Le cas le plus typique que j'ai vu c'est pour déprécier un identifiant en introduisant un remplaçant. Pendant la période de transition les 2 co-existent.
    Ou une liste d'identifiants nommés et un Default qui pointerait vers un existant.
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  7. #7
    Membre Expert
    Femme Profil pro
    ..
    Inscrit en
    Décembre 2019
    Messages
    667
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Âge : 95
    Localisation : Autre

    Informations professionnelles :
    Activité : ..

    Informations forums :
    Inscription : Décembre 2019
    Messages : 667
    Par défaut
    Salut,

    Citation Envoyé par koala01 Voir le message
    ...
    Ton message ne prouve rien, au mieux c'est juste une illustration du raisonnement fallacieux (conf. le double sens).
    Concrètement tu as pris une pomme, tu l'as recouverte d'une peau d'orange et tu t'étonnes que certains clients se font avoir (ton if) et d'autres pas (ton switch).
    Si au lieu de vouloir faire passer des pommes pour des oranges, tu avais ajouté un énumerateur la_promotion_du_jour_par_défaut, tu aurais bien eu tes deux énumerateurs de même valeur mais surtout tu aurais eu moins de facilité à faire passer les problèmes que tu décris pour autre chose qu'une grossière erreur de logique. Accessoirement, j'en vois encore deux autres rien que dans ton énumération.

    On peut faire plein de chose avec les énumérations. Et pour définir des constantes de même nature c'est très bien.

    Aussi, l'auteur du sujet parle de registres. Disons qu'il décide de définir RegA32 et RegA64 pour des raisons d'efficacités et de portabilités. Et bien sur un système purement 64bits, ces deux entités auront la même valeur. De la sorte, il tire avantage du matériel sans sacrifier ni le code, ni les performances.

  8. #8
    Expert confirmé
    Homme Profil pro
    Analyste/ Programmeur
    Inscrit en
    Juillet 2013
    Messages
    4 746
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Analyste/ Programmeur

    Informations forums :
    Inscription : Juillet 2013
    Messages : 4 746
    Par défaut
    Citation Envoyé par kaitlyn Voir le message
    tu avais ajouté un énumerateur la_promotion_du_jour_par_défaut, tu aurais bien eu tes deux énumerateurs de même valeur mais surtout tu aurais eu moins de facilité à faire passer les problèmes que tu décris pour autre chose qu'une grossière erreur de logique.
    il faudrait 1 peu expliquer : lorsque tu parles de promotion, tu penses au shampoing "2 en 1"

    Citation Envoyé par kaitlyn Voir le message
    Aussi, l'auteur du sujet parle de registres. Disons qu'il décide de définir RegA32 et RegA64 pour des raisons d'efficacités et de portabilités. Et bien sur un système purement 64bits, ces deux entités auront la même valeur. De la sorte, il tire avantage du matériel sans sacrifier ni le code, ni les performances.
    Dans ce cas là je pense que le mieux est de faire 2 entêtes différentes (archi_trucmuche.h et archi_tartempion.h) contenant les mêmes "choses" qui diffèrent : structures, unions, énumérations, define, …
    Les directives préprocesseurs (#if #ifdef #ifndef #else #elif #endif …) peuvent être également 1 solution, mais 1 peu "lourdes".
    Après, la maintenance sera toujours à faire précautionneusement

  9. #9
    Membre Expert
    Femme Profil pro
    ..
    Inscrit en
    Décembre 2019
    Messages
    667
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Âge : 95
    Localisation : Autre

    Informations professionnelles :
    Activité : ..

    Informations forums :
    Inscription : Décembre 2019
    Messages : 667
    Par défaut
    Salut toi,

    La promotion est un énumérateur. La pomme est en promotion par exemple. C'est tout.

  10. #10
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2009
    Messages
    4 491
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 491
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par pcdwarf Voir le message
    J'écris un driver pour un micro-contrôleur

    À l'intérieur, j'ai un grand nombre d'étiquettes pour nommer des valeurs comme par exemple des adresses de registres et des numéros de bits.

    Je peux utiliser le préprocesseur avec des #define. C'est la méthode classique, mais ça pose le problème de devoir gérer l'unicité des étiquettes
    Hello

    Je ne sais pas quel MCU tu utilises, mais en général les fondeurs fournissent toutes ces définitions dans des fichiers headers C, avec des #defines, et ils ont fait l'effort que tous les identifiants soient uniques. Si tu n'utilises pas le fichier correspondant à ton MCU, je t'encourage à le faire et à t''éviter ainsi un travail laborieux et très error-prone. Si ton fondeur ne fournit pas de tel fichier, alors il sera temps de le faire à la main.

    De manière générale, on préfère des constexpr au #define pour définir des constantes en C++. C'est équivalent une fois compilé mais c'est beaucoup plus robuste pour écrire du code. Toutefois, dans ton cas, ce n'est pas forcément la bonne / meilleure solution.

    Pour commencer, tu vas rencontrer un premier problème avec les adresses : il est impossible d'utiliser des reinterpret_cast dans un contexte constexpr. Ainsi, impossible de faire quelque chose comme :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    constexpr std::uint32_t* TIMER1_ADDRESS = reinterpret_cast<std::uint32_t*>(0x800810);
    Ensuite, il faut aussi penser à la facilité d'écrire du code robuste. Voici un premier exemple qui compile sans erreur ni warning mais qui est bien sûr faux :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    #include <cstdint>
     
    constexpr std::intptr_t TIMER0_ADDRESS = 0x800800;
    constexpr std::intptr_t TIMER1_ADDRESS = 0x800810;
    constexpr std::intptr_t USART0_ADDRESS = 0x800A00;
     
    void init_usart(std::intptr_t usart_address) {
        (void) usart_address;
    }
     
    int main() {
        init_usart(TIMER0_ADDRESS); // oopsi!
    }
    Pour éviter de telles erreurs, des énumérations peuvent être une bonne manière de typer les adresses. Voici un 2e example, toujours faux, mais qui ne compile pas (et c'est vraiment un gros plus !) :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    #include <cstdint>
     
    enum class TimerAddress : std::intptr_t {
        timer0 = 0x800800,
        timer1 = 0x800810
    };
     
    enum class UsartAddress : std::intptr_t {
        usart0 = 0x800A00
    };
     
    void init_usart(UsartAddress usart_address) {
        (void) usart_address;
    }
     
    int main() {
        init_usart(TimerAddress::timer0); // catch it!
    }
    Pour la culture, je conseille de regarder ce que raconte Dan Saks dans ses conférences. Il milite depuis longtemps pour une utilisation de C++ pour faire des drivers embedded en C++ et dit des choses qui font réfléchir. Exemple d'une conférence récente :

  11. #11
    Membre Expert
    Femme Profil pro
    ..
    Inscrit en
    Décembre 2019
    Messages
    667
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Âge : 95
    Localisation : Autre

    Informations professionnelles :
    Activité : ..

    Informations forums :
    Inscription : Décembre 2019
    Messages : 667
    Par défaut
    Pourquoi les dates de modifications des messages ne sont pas publiques ?
    Je demande ça parce que je n'ai pas souvenir que Bousk avait parlé d'énumérateur par défaut dans son message, du coup ça me donne l'impression d'avoir posté pour rien. En tout cas je ne me serais pas embêtée à essayer de montrer (peut-être pas toujours avec tact, désolée ) en quoi le raisonnement de koala01 est erroné. D'ailleurs avec des "#define" ou des "const", il en serait arrivé à la même conclusion.

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Différence entre #define et static const int
    Par memoire.ph dans le forum Débuter
    Réponses: 6
    Dernier message: 02/04/2012, 01h05
  2. Différence entre quelques Methodes Static en java
    Par Echap dans le forum Débuter avec Java
    Réponses: 11
    Dernier message: 14/03/2011, 15h01
  3. variable static différence entre .NET et PHP
    Par Sayrus dans le forum C#
    Réponses: 5
    Dernier message: 04/10/2008, 00h44
  4. Différence entre public static
    Par moooona dans le forum Débuter avec Java
    Réponses: 1
    Dernier message: 24/05/2008, 15h23
  5. Différences entre #define et const
    Par Tittom dans le forum C
    Réponses: 19
    Dernier message: 01/06/2006, 13h48

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo